Skip to main content

proof_engine/editor/
level_streaming_editor.rs

1#[allow(dead_code, unused_variables, unused_mut, unused_imports)]
2
3use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
4use std::collections::{HashMap, VecDeque, HashSet, BTreeMap};
5
6// ============================================================
7// CONSTANTS
8// ============================================================
9
10const MAX_STREAMING_LEVELS: usize = 512;
11const MAX_CELLS_PER_AXIS: usize = 256;
12const DEFAULT_CELL_SIZE: f32 = 512.0;
13const MAX_MEMORY_BUDGET_MB: f32 = 2048.0;
14const STREAMING_HYSTERESIS: f32 = 50.0;
15const MAX_CONCURRENT_LOADS: usize = 4;
16const PREFETCH_LOOKAHEAD_SECONDS: f32 = 2.0;
17const HZB_MAX_MIPS: usize = 8;
18const MAX_SECTOR_PORTALS: usize = 32;
19const LOD_BIAS_DISTANCE_SCALE: f32 = 0.001;
20const MAX_DEPENDENCY_DEPTH: usize = 64;
21const BANDWIDTH_ESTIMATE_WINDOW: usize = 60;
22const LEVEL_TIMELINE_CAPACITY: usize = 1024;
23
24// ============================================================
25// ENUMS
26// ============================================================
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum StreamingState {
30    Unloaded,
31    Queued,
32    Loading,
33    Loaded,
34    Unloading,
35    Failed,
36    Evicted,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum LoadPriority {
41    Critical = 0,
42    High = 1,
43    Medium = 2,
44    Low = 3,
45    Prefetch = 4,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum LodLevel {
50    Lod0 = 0,
51    Lod1 = 1,
52    Lod2 = 2,
53    Lod3 = 3,
54    Lod4 = 4,
55    Culled = 5,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum VolumeShape {
60    Box,
61    Sphere,
62    ConvexHull,
63    Cylinder,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum SectorTransitionType {
68    Immediate,
69    Fade,
70    Portal,
71    Teleport,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum LevelPersistence {
76    AlwaysLoaded,
77    Dynamic,
78    Transient,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum EvictionPolicy {
83    Lru,
84    Lfu,
85    Distance,
86    Priority,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq)]
90pub enum DependencyEdgeType {
91    HardDependency,
92    SoftDependency,
93    Optional,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum DebugOverlay {
98    None,
99    StreamingState,
100    MemoryUsage,
101    LoadDistance,
102    CellGrid,
103    OcclusionHzb,
104    FrustumCulled,
105    PriorityHeatmap,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum StreamingEventKind {
110    LevelQueued,
111    LevelLoadStarted,
112    LevelLoadCompleted,
113    LevelUnloadStarted,
114    LevelUnloadCompleted,
115    LevelLoadFailed,
116    MemoryPressure,
117    BudgetExceeded,
118    PrefetchHit,
119    PrefetchMiss,
120}
121
122// ============================================================
123// CORE DATA STRUCTURES
124// ============================================================
125
126#[derive(Debug, Clone)]
127pub struct Aabb {
128    pub min: Vec3,
129    pub max: Vec3,
130}
131
132impl Aabb {
133    pub fn new(min: Vec3, max: Vec3) -> Self {
134        Self { min, max }
135    }
136
137    pub fn center(&self) -> Vec3 {
138        (self.min + self.max) * 0.5
139    }
140
141    pub fn extents(&self) -> Vec3 {
142        (self.max - self.min) * 0.5
143    }
144
145    pub fn size(&self) -> Vec3 {
146        self.max - self.min
147    }
148
149    pub fn surface_area(&self) -> f32 {
150        let s = self.size();
151        2.0 * (s.x * s.y + s.y * s.z + s.z * s.x)
152    }
153
154    pub fn volume(&self) -> f32 {
155        let s = self.size();
156        s.x * s.y * s.z
157    }
158
159    pub fn contains_point(&self, p: Vec3) -> bool {
160        p.x >= self.min.x && p.x <= self.max.x
161            && p.y >= self.min.y && p.y <= self.max.y
162            && p.z >= self.min.z && p.z <= self.max.z
163    }
164
165    pub fn intersects(&self, other: &Aabb) -> bool {
166        self.min.x <= other.max.x && self.max.x >= other.min.x
167            && self.min.y <= other.max.y && self.max.y >= other.min.y
168            && self.min.z <= other.max.z && self.max.z >= other.min.z
169    }
170
171    pub fn expand_by(&self, amount: f32) -> Aabb {
172        Aabb {
173            min: self.min - Vec3::splat(amount),
174            max: self.max + Vec3::splat(amount),
175        }
176    }
177
178    pub fn distance_sq_to_point(&self, p: Vec3) -> f32 {
179        let dx = (self.min.x - p.x).max(0.0).max(p.x - self.max.x);
180        let dy = (self.min.y - p.y).max(0.0).max(p.y - self.max.y);
181        let dz = (self.min.z - p.z).max(0.0).max(p.z - self.max.z);
182        dx * dx + dy * dy + dz * dz
183    }
184
185    pub fn distance_to_point(&self, p: Vec3) -> f32 {
186        self.distance_sq_to_point(p).sqrt()
187    }
188
189    pub fn merge(&self, other: &Aabb) -> Aabb {
190        Aabb {
191            min: self.min.min(other.min),
192            max: self.max.max(other.max),
193        }
194    }
195
196    pub fn from_center_extents(center: Vec3, extents: Vec3) -> Self {
197        Self {
198            min: center - extents,
199            max: center + extents,
200        }
201    }
202}
203
204#[derive(Debug, Clone)]
205pub struct Sphere {
206    pub center: Vec3,
207    pub radius: f32,
208}
209
210impl Sphere {
211    pub fn new(center: Vec3, radius: f32) -> Self {
212        Self { center, radius }
213    }
214
215    pub fn contains_point(&self, p: Vec3) -> bool {
216        (p - self.center).length_squared() <= self.radius * self.radius
217    }
218
219    pub fn intersects_aabb(&self, aabb: &Aabb) -> bool {
220        let dist_sq = aabb.distance_sq_to_point(self.center);
221        dist_sq <= self.radius * self.radius
222    }
223
224    pub fn intersects_sphere(&self, other: &Sphere) -> bool {
225        let r = self.radius + other.radius;
226        (self.center - other.center).length_squared() <= r * r
227    }
228}
229
230#[derive(Debug, Clone)]
231pub struct FrustumPlane {
232    pub normal: Vec3,
233    pub distance: f32,
234}
235
236impl FrustumPlane {
237    pub fn new(normal: Vec3, distance: f32) -> Self {
238        let len = normal.length();
239        Self {
240            normal: if len > 1e-6 { normal / len } else { normal },
241            distance: if len > 1e-6 { distance / len } else { distance },
242        }
243    }
244
245    pub fn signed_distance_to(&self, p: Vec3) -> f32 {
246        self.normal.dot(p) + self.distance
247    }
248}
249
250#[derive(Debug, Clone)]
251pub struct Frustum {
252    pub planes: [FrustumPlane; 6],
253}
254
255impl Frustum {
256    /// Build frustum from view-projection matrix
257    pub fn from_view_proj(vp: Mat4) -> Self {
258        let cols = vp.to_cols_array_2d();
259        // rows of vp for plane extraction
260        let r0 = Vec4::new(cols[0][0], cols[1][0], cols[2][0], cols[3][0]);
261        let r1 = Vec4::new(cols[0][1], cols[1][1], cols[2][1], cols[3][1]);
262        let r2 = Vec4::new(cols[0][2], cols[1][2], cols[2][2], cols[3][2]);
263        let r3 = Vec4::new(cols[0][3], cols[1][3], cols[2][3], cols[3][3]);
264
265        let left   = r3 + r0;
266        let right  = r3 - r0;
267        let bottom = r3 + r1;
268        let top    = r3 - r1;
269        let near   = r3 + r2;
270        let far    = r3 - r2;
271
272        let make = |v: Vec4| FrustumPlane::new(Vec3::new(v.x, v.y, v.z), v.w);
273
274        Self {
275            planes: [
276                make(left),
277                make(right),
278                make(bottom),
279                make(top),
280                make(near),
281                make(far),
282            ],
283        }
284    }
285
286    pub fn test_aabb(&self, aabb: &Aabb) -> bool {
287        let c = aabb.center();
288        let e = aabb.extents();
289        for plane in &self.planes {
290            let r = e.x * plane.normal.x.abs()
291                + e.y * plane.normal.y.abs()
292                + e.z * plane.normal.z.abs();
293            let d = plane.signed_distance_to(c);
294            if d + r < 0.0 {
295                return false;
296            }
297        }
298        true
299    }
300
301    pub fn test_sphere(&self, sphere: &Sphere) -> bool {
302        for plane in &self.planes {
303            if plane.signed_distance_to(sphere.center) < -sphere.radius {
304                return false;
305            }
306        }
307        true
308    }
309
310    pub fn test_point(&self, p: Vec3) -> bool {
311        for plane in &self.planes {
312            if plane.signed_distance_to(p) < 0.0 {
313                return false;
314            }
315        }
316        true
317    }
318}
319
320#[derive(Debug, Clone)]
321pub struct ConvexHull {
322    pub planes: Vec<FrustumPlane>,
323    pub vertices: Vec<Vec3>,
324}
325
326impl ConvexHull {
327    pub fn new(vertices: Vec<Vec3>) -> Self {
328        // Build convex hull planes from vertices (simplified — assume convex input)
329        let mut planes = Vec::new();
330        // Use centroid for orientation
331        let centroid = if !vertices.is_empty() {
332            vertices.iter().fold(Vec3::ZERO, |acc, &v| acc + v) / vertices.len() as f32
333        } else {
334            Vec3::ZERO
335        };
336
337        // Build face normals for a simple convex polyhedron (triangulated)
338        let n = vertices.len();
339        for i in 0..n {
340            for j in (i + 1)..n {
341                for k in (j + 1)..n {
342                    let a = vertices[i];
343                    let b = vertices[j];
344                    let c = vertices[k];
345                    let normal = (b - a).cross(c - a);
346                    if normal.length_squared() < 1e-10 {
347                        continue;
348                    }
349                    let n = normal.normalize();
350                    let d = -n.dot(a);
351                    // Ensure normal points outward from centroid
352                    if n.dot(centroid) + d > 0.0 {
353                        planes.push(FrustumPlane::new(-n, -d));
354                    } else {
355                        planes.push(FrustumPlane::new(n, d));
356                    }
357                    break;
358                }
359                break;
360            }
361            break;
362        }
363
364        Self { planes, vertices }
365    }
366
367    pub fn contains_point(&self, p: Vec3) -> bool {
368        for plane in &self.planes {
369            if plane.signed_distance_to(p) < 0.0 {
370                return false;
371            }
372        }
373        true
374    }
375
376    pub fn intersects_aabb(&self, aabb: &Aabb) -> bool {
377        let c = aabb.center();
378        let e = aabb.extents();
379        for plane in &self.planes {
380            let r = e.x * plane.normal.x.abs()
381                + e.y * plane.normal.y.abs()
382                + e.z * plane.normal.z.abs();
383            if plane.signed_distance_to(c) + r < 0.0 {
384                return false;
385            }
386        }
387        true
388    }
389}
390
391// ============================================================
392// HZB (HIERARCHICAL Z BUFFER) OCCLUSION SYSTEM
393// ============================================================
394
395#[derive(Debug, Clone)]
396pub struct HzbMipLevel {
397    pub width: usize,
398    pub height: usize,
399    pub data: Vec<f32>, // min-z per tile
400}
401
402impl HzbMipLevel {
403    pub fn new(width: usize, height: usize) -> Self {
404        Self {
405            width,
406            height,
407            data: vec![1.0f32; width * height],
408        }
409    }
410
411    pub fn sample(&self, u: f32, v: f32) -> f32 {
412        let px = ((u * self.width as f32) as usize).min(self.width.saturating_sub(1));
413        let py = ((v * self.height as f32) as usize).min(self.height.saturating_sub(1));
414        self.data[py * self.width + px]
415    }
416
417    pub fn sample_bilinear(&self, u: f32, v: f32) -> f32 {
418        let x = u * (self.width as f32 - 1.0);
419        let y = v * (self.height as f32 - 1.0);
420        let x0 = (x as usize).min(self.width.saturating_sub(1));
421        let y0 = (y as usize).min(self.height.saturating_sub(1));
422        let x1 = (x0 + 1).min(self.width.saturating_sub(1));
423        let y1 = (y0 + 1).min(self.height.saturating_sub(1));
424        let fx = x - x0 as f32;
425        let fy = y - y0 as f32;
426        let v00 = self.data[y0 * self.width + x0];
427        let v10 = self.data[y0 * self.width + x1];
428        let v01 = self.data[y1 * self.width + x0];
429        let v11 = self.data[y1 * self.width + x1];
430        v00 * (1.0 - fx) * (1.0 - fy)
431            + v10 * fx * (1.0 - fy)
432            + v01 * (1.0 - fx) * fy
433            + v11 * fx * fy
434    }
435}
436
437#[derive(Debug, Clone)]
438pub struct HierarchicalZBuffer {
439    pub mips: Vec<HzbMipLevel>,
440    pub base_width: usize,
441    pub base_height: usize,
442}
443
444impl HierarchicalZBuffer {
445    pub fn new(width: usize, height: usize) -> Self {
446        let mut mips = Vec::new();
447        let mut w = width;
448        let mut h = height;
449        for _ in 0..HZB_MAX_MIPS {
450            mips.push(HzbMipLevel::new(w, h));
451            w = (w / 2).max(1);
452            h = (h / 2).max(1);
453            if w == 1 && h == 1 {
454                mips.push(HzbMipLevel::new(1, 1));
455                break;
456            }
457        }
458        Self { mips, base_width: width, base_height: height }
459    }
460
461    /// Build mip hierarchy from base depth buffer via min-reduction
462    pub fn build_from_depth(&mut self, depth: &[f32]) {
463        if self.mips.is_empty() { return; }
464        let w = self.base_width;
465        let h = self.base_height;
466        // Fill mip 0 from depth
467        let mip0 = &mut self.mips[0];
468        let len = (w * h).min(depth.len()).min(mip0.data.len());
469        mip0.data[..len].copy_from_slice(&depth[..len]);
470
471        // Build subsequent mips
472        for i in 1..self.mips.len() {
473            let pw = self.mips[i - 1].width;
474            let ph = self.mips[i - 1].height;
475            let nw = (pw / 2).max(1);
476            let nh = (ph / 2).max(1);
477            let prev_data = self.mips[i - 1].data.clone();
478            let cur = &mut self.mips[i];
479            cur.width = nw;
480            cur.height = nh;
481            cur.data.resize(nw * nh, 1.0);
482            for y in 0..nh {
483                for x in 0..nw {
484                    let sx = (x * 2).min(pw.saturating_sub(1));
485                    let sy = (y * 2).min(ph.saturating_sub(1));
486                    let sx1 = (sx + 1).min(pw.saturating_sub(1));
487                    let sy1 = (sy + 1).min(ph.saturating_sub(1));
488                    let v00 = prev_data[sy * pw + sx];
489                    let v10 = prev_data[sy * pw + sx1];
490                    let v01 = prev_data[sy1 * pw + sx];
491                    let v11 = prev_data[sy1 * pw + sx1];
492                    // min-z reduction (closest depth wins)
493                    cur.data[y * nw + x] = v00.min(v10).min(v01).min(v11);
494                }
495            }
496        }
497    }
498
499    /// Test an AABB against HZB. Returns true if potentially visible.
500    pub fn test_aabb_visibility(&self, aabb: &Aabb, view_proj: &Mat4) -> bool {
501        if self.mips.is_empty() { return true; }
502
503        // Project AABB corners into NDC, find screen-space bounding rect
504        let corners = [
505            Vec3::new(aabb.min.x, aabb.min.y, aabb.min.z),
506            Vec3::new(aabb.max.x, aabb.min.y, aabb.min.z),
507            Vec3::new(aabb.min.x, aabb.max.y, aabb.min.z),
508            Vec3::new(aabb.max.x, aabb.max.y, aabb.min.z),
509            Vec3::new(aabb.min.x, aabb.min.y, aabb.max.z),
510            Vec3::new(aabb.max.x, aabb.min.y, aabb.max.z),
511            Vec3::new(aabb.min.x, aabb.max.y, aabb.max.z),
512            Vec3::new(aabb.max.x, aabb.max.y, aabb.max.z),
513        ];
514
515        let mut min_x = f32::MAX;
516        let mut min_y = f32::MAX;
517        let mut max_x = f32::MIN;
518        let mut max_y = f32::MIN;
519        let mut min_z = f32::MAX;
520        let mut all_behind = true;
521
522        for &c in &corners {
523            let clip = *view_proj * Vec4::new(c.x, c.y, c.z, 1.0);
524            if clip.w <= 0.0 { continue; }
525            all_behind = false;
526            let ndc = Vec3::new(clip.x / clip.w, clip.y / clip.w, clip.z / clip.w);
527            let u = (ndc.x * 0.5 + 0.5).clamp(0.0, 1.0);
528            let v = (1.0 - (ndc.y * 0.5 + 0.5)).clamp(0.0, 1.0);
529            min_x = min_x.min(u);
530            min_y = min_y.min(v);
531            max_x = max_x.max(u);
532            max_y = max_y.max(v);
533            min_z = min_z.min(ndc.z.clamp(0.0, 1.0));
534        }
535
536        if all_behind { return false; }
537
538        // Pick appropriate mip level based on screen-space coverage
539        let w_uv = max_x - min_x;
540        let h_uv = max_y - min_y;
541        let max_dim = w_uv.max(h_uv);
542        let mip = if max_dim <= 0.0 {
543            self.mips.len() - 1
544        } else {
545            let level = (-max_dim.log2()).max(0.0) as usize;
546            level.min(self.mips.len() - 1)
547        };
548
549        let mip_data = &self.mips[mip];
550        // Sample max-depth in screen-rect to test against our min projected depth
551        let x0 = ((min_x * mip_data.width as f32) as usize).min(mip_data.width.saturating_sub(1));
552        let x1 = ((max_x * mip_data.width as f32) as usize).min(mip_data.width.saturating_sub(1));
553        let y0 = ((min_y * mip_data.height as f32) as usize).min(mip_data.height.saturating_sub(1));
554        let y1 = ((max_y * mip_data.height as f32) as usize).min(mip_data.height.saturating_sub(1));
555
556        let mut occluder_depth = f32::MIN;
557        for y in y0..=y1 {
558            for x in x0..=x1 {
559                let d = mip_data.data[y * mip_data.width + x];
560                occluder_depth = occluder_depth.max(d);
561            }
562        }
563
564        // Visible if our closest point is in front of occluder
565        min_z <= occluder_depth + 1e-4
566    }
567}
568
569// ============================================================
570// STREAMING LEVEL
571// ============================================================
572
573#[derive(Debug, Clone)]
574pub struct StreamingLevelAsset {
575    pub id: u64,
576    pub name: String,
577    pub file_path: String,
578    pub size_bytes: u64,
579    pub uncompressed_size_bytes: u64,
580    pub dependencies: Vec<u64>,
581    pub load_time_estimate_ms: f32,
582}
583
584#[derive(Debug, Clone)]
585pub struct StreamingLevel {
586    pub id: u64,
587    pub name: String,
588    pub asset: StreamingLevelAsset,
589    pub bounds: Aabb,
590    pub sphere_bounds: Sphere,
591    pub load_distance: f32,
592    pub unload_distance: f32,
593    pub priority: LoadPriority,
594    pub persistence: LevelPersistence,
595    pub state: StreamingState,
596    pub lod_bias: f32,
597    pub memory_footprint_mb: f32,
598    pub current_lod: LodLevel,
599    pub sector_id: Option<u64>,
600    pub load_timestamp_ms: f64,
601    pub unload_timestamp_ms: f64,
602    pub load_count: u32,
603    pub transform: Mat4,
604    pub is_visible: bool,
605    pub is_frustum_culled: bool,
606    pub is_occlusion_culled: bool,
607    pub distance_to_camera: f32,
608    pub screen_size: f32,
609    pub importance_weight: f32,
610}
611
612impl StreamingLevel {
613    pub fn new(id: u64, name: String, asset: StreamingLevelAsset, bounds: Aabb) -> Self {
614        let center = bounds.center();
615        let radius = bounds.extents().length();
616        Self {
617            id,
618            name,
619            asset,
620            bounds: bounds.clone(),
621            sphere_bounds: Sphere::new(center, radius),
622            load_distance: 1000.0,
623            unload_distance: 1200.0,
624            priority: LoadPriority::Medium,
625            persistence: LevelPersistence::Dynamic,
626            state: StreamingState::Unloaded,
627            lod_bias: 0.0,
628            memory_footprint_mb: 0.0,
629            current_lod: LodLevel::Culled,
630            sector_id: None,
631            load_timestamp_ms: 0.0,
632            unload_timestamp_ms: 0.0,
633            load_count: 0,
634            transform: Mat4::IDENTITY,
635            is_visible: false,
636            is_frustum_culled: false,
637            is_occlusion_culled: false,
638            distance_to_camera: f32::MAX,
639            screen_size: 0.0,
640            importance_weight: 1.0,
641        }
642    }
643
644    pub fn compute_lod(&self, distance: f32, lod_bias: f32) -> LodLevel {
645        let adjusted = distance * (1.0 + lod_bias * LOD_BIAS_DISTANCE_SCALE);
646        if adjusted < 100.0 { LodLevel::Lod0 }
647        else if adjusted < 300.0 { LodLevel::Lod1 }
648        else if adjusted < 600.0 { LodLevel::Lod2 }
649        else if adjusted < 1000.0 { LodLevel::Lod3 }
650        else if adjusted < self.load_distance { LodLevel::Lod4 }
651        else { LodLevel::Culled }
652    }
653
654    pub fn compute_screen_size(&self, camera_pos: Vec3, fov_y_rad: f32, viewport_height: f32) -> f32 {
655        let dist = (self.sphere_bounds.center - camera_pos).length().max(0.01);
656        let angular_size = 2.0 * (self.sphere_bounds.radius / dist).atan();
657        let pixels = (angular_size / fov_y_rad) * viewport_height;
658        pixels / viewport_height
659    }
660
661    pub fn should_load(&self, camera_pos: Vec3) -> bool {
662        match self.persistence {
663            LevelPersistence::AlwaysLoaded => true,
664            _ => self.bounds.distance_to_point(camera_pos) < self.load_distance,
665        }
666    }
667
668    pub fn should_unload(&self, camera_pos: Vec3) -> bool {
669        match self.persistence {
670            LevelPersistence::AlwaysLoaded => false,
671            _ => self.bounds.distance_to_point(camera_pos) > self.unload_distance,
672        }
673    }
674
675    pub fn memory_estimate_mb(&self) -> f32 {
676        let base = self.asset.size_bytes as f32 / (1024.0 * 1024.0);
677        match self.current_lod {
678            LodLevel::Lod0 => base,
679            LodLevel::Lod1 => base * 0.7,
680            LodLevel::Lod2 => base * 0.4,
681            LodLevel::Lod3 => base * 0.2,
682            LodLevel::Lod4 => base * 0.1,
683            LodLevel::Culled => 0.0,
684        }
685    }
686}
687
688// ============================================================
689// WORLD PARTITION GRID
690// ============================================================
691
692#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
693pub struct CellCoord {
694    pub x: i32,
695    pub y: i32,
696    pub z: i32,
697}
698
699impl CellCoord {
700    pub fn new(x: i32, y: i32, z: i32) -> Self {
701        Self { x, y, z }
702    }
703
704    pub fn neighbors_2d(&self) -> [CellCoord; 8] {
705        [
706            CellCoord::new(self.x - 1, self.y - 1, self.z),
707            CellCoord::new(self.x,     self.y - 1, self.z),
708            CellCoord::new(self.x + 1, self.y - 1, self.z),
709            CellCoord::new(self.x - 1, self.y,     self.z),
710            CellCoord::new(self.x + 1, self.y,     self.z),
711            CellCoord::new(self.x - 1, self.y + 1, self.z),
712            CellCoord::new(self.x,     self.y + 1, self.z),
713            CellCoord::new(self.x + 1, self.y + 1, self.z),
714        ]
715    }
716
717    pub fn neighbors_3d(&self) -> Vec<CellCoord> {
718        let mut result = Vec::with_capacity(26);
719        for dz in -1i32..=1 {
720            for dy in -1i32..=1 {
721                for dx in -1i32..=1 {
722                    if dx == 0 && dy == 0 && dz == 0 { continue; }
723                    result.push(CellCoord::new(self.x + dx, self.y + dy, self.z + dz));
724                }
725            }
726        }
727        result
728    }
729
730    pub fn manhattan_distance(&self, other: &CellCoord) -> i32 {
731        (self.x - other.x).abs() + (self.y - other.y).abs() + (self.z - other.z).abs()
732    }
733
734    pub fn chebyshev_distance(&self, other: &CellCoord) -> i32 {
735        let dx = (self.x - other.x).abs();
736        let dy = (self.y - other.y).abs();
737        let dz = (self.z - other.z).abs();
738        dx.max(dy).max(dz)
739    }
740}
741
742#[derive(Debug, Clone)]
743pub struct WorldCell {
744    pub coord: CellCoord,
745    pub bounds: Aabb,
746    pub level_ids: Vec<u64>,
747    pub dynamic_object_ids: Vec<u64>,
748    pub memory_used_mb: f32,
749    pub is_active: bool,
750    pub load_priority_score: f32,
751    pub last_accessed_frame: u64,
752}
753
754impl WorldCell {
755    pub fn new(coord: CellCoord, cell_size: f32) -> Self {
756        let min = Vec3::new(
757            coord.x as f32 * cell_size,
758            coord.z as f32 * cell_size,
759            coord.y as f32 * cell_size,
760        );
761        let max = min + Vec3::splat(cell_size);
762        Self {
763            coord,
764            bounds: Aabb::new(min, max),
765            level_ids: Vec::new(),
766            dynamic_object_ids: Vec::new(),
767            memory_used_mb: 0.0,
768            is_active: false,
769            load_priority_score: 0.0,
770            last_accessed_frame: 0,
771        }
772    }
773
774    pub fn center(&self) -> Vec3 {
775        self.bounds.center()
776    }
777
778    pub fn compute_priority_score(&mut self, camera_pos: Vec3, camera_dir: Vec3) -> f32 {
779        let dist = self.bounds.distance_to_point(camera_pos).max(0.01);
780        let to_cell = (self.center() - camera_pos).normalize_or_zero();
781        let dot = camera_dir.dot(to_cell).clamp(0.0, 1.0);
782        // Score: high if close and in front of camera
783        let score = (1.0 / dist) * (0.5 + 0.5 * dot);
784        self.load_priority_score = score;
785        score
786    }
787}
788
789#[derive(Debug)]
790pub struct WorldPartitionGrid {
791    pub cell_size: f32,
792    pub cells: HashMap<CellCoord, WorldCell>,
793    pub spatial_hash: HashMap<u64, CellCoord>, // object_id -> cell
794    pub origin: Vec3,
795    pub active_radius_cells: i32,
796}
797
798impl WorldPartitionGrid {
799    pub fn new(cell_size: f32, origin: Vec3) -> Self {
800        Self {
801            cell_size,
802            cells: HashMap::new(),
803            spatial_hash: HashMap::new(),
804            origin,
805            active_radius_cells: 4,
806        }
807    }
808
809    pub fn world_to_cell(&self, pos: Vec3) -> CellCoord {
810        let rel = pos - self.origin;
811        CellCoord::new(
812            (rel.x / self.cell_size).floor() as i32,
813            (rel.z / self.cell_size).floor() as i32,
814            (rel.y / self.cell_size).floor() as i32,
815        )
816    }
817
818    pub fn cell_to_world_center(&self, coord: CellCoord) -> Vec3 {
819        Vec3::new(
820            self.origin.x + (coord.x as f32 + 0.5) * self.cell_size,
821            self.origin.y + (coord.z as f32 + 0.5) * self.cell_size,
822            self.origin.z + (coord.y as f32 + 0.5) * self.cell_size,
823        )
824    }
825
826    pub fn get_or_create_cell(&mut self, coord: CellCoord) -> &mut WorldCell {
827        let cell_size = self.cell_size;
828        self.cells.entry(coord).or_insert_with(|| WorldCell::new(coord, cell_size))
829    }
830
831    pub fn get_cells_in_radius(&self, center: Vec3, radius: f32) -> Vec<CellCoord> {
832        let coord = self.world_to_cell(center);
833        let cell_radius = (radius / self.cell_size).ceil() as i32 + 1;
834        let mut result = Vec::new();
835        for dz in -cell_radius..=cell_radius {
836            for dy in -cell_radius..=cell_radius {
837                for dx in -cell_radius..=cell_radius {
838                    let c = CellCoord::new(coord.x + dx, coord.y + dy, coord.z + dz);
839                    if let Some(cell) = self.cells.get(&c) {
840                        if cell.bounds.distance_to_point(center) <= radius {
841                            result.push(c);
842                        }
843                    } else {
844                        // Cell doesn't exist yet but check if it could be in range
845                        let cell_center = self.cell_to_world_center(c);
846                        if (cell_center - center).length() <= radius + self.cell_size {
847                            result.push(c);
848                        }
849                    }
850                }
851            }
852        }
853        result
854    }
855
856    pub fn register_object(&mut self, object_id: u64, pos: Vec3) {
857        let new_coord = self.world_to_cell(pos);
858        // Remove from old cell if it exists
859        if let Some(&old_coord) = self.spatial_hash.get(&object_id) {
860            if old_coord != new_coord {
861                if let Some(cell) = self.cells.get_mut(&old_coord) {
862                    cell.dynamic_object_ids.retain(|&id| id != object_id);
863                }
864            }
865        }
866        self.spatial_hash.insert(object_id, new_coord);
867        let cell = self.get_or_create_cell(new_coord);
868        if !cell.dynamic_object_ids.contains(&object_id) {
869            cell.dynamic_object_ids.push(object_id);
870        }
871    }
872
873    pub fn unregister_object(&mut self, object_id: u64) {
874        if let Some(coord) = self.spatial_hash.remove(&object_id) {
875            if let Some(cell) = self.cells.get_mut(&coord) {
876                cell.dynamic_object_ids.retain(|&id| id != object_id);
877            }
878        }
879    }
880
881    pub fn query_objects_in_radius(&self, pos: Vec3, radius: f32) -> Vec<u64> {
882        let cells = self.get_cells_in_radius(pos, radius);
883        let mut result = Vec::new();
884        for coord in cells {
885            if let Some(cell) = self.cells.get(&coord) {
886                for &oid in &cell.dynamic_object_ids {
887                    result.push(oid);
888                }
889            }
890        }
891        result
892    }
893
894    pub fn optimize_cell_size(&self, level_count: usize, world_size: f32) -> f32 {
895        // Heuristic: aim for ~4-8 levels per cell
896        let target_cells = (level_count / 6).max(1);
897        let cells_per_axis = (target_cells as f32).cbrt().ceil() as usize;
898        (world_size / cells_per_axis as f32).max(64.0)
899    }
900
901    pub fn compute_total_memory_mb(&self) -> f32 {
902        self.cells.values().map(|c| c.memory_used_mb).sum()
903    }
904
905    pub fn get_neighbor_cells(&self, coord: CellCoord) -> Vec<&WorldCell> {
906        coord.neighbors_3d()
907            .into_iter()
908            .filter_map(|c| self.cells.get(&c))
909            .collect()
910    }
911}
912
913// ============================================================
914// STREAMING VOLUMES
915// ============================================================
916
917#[derive(Debug, Clone)]
918pub struct StreamingVolumeBox {
919    pub transform: Mat4,
920    pub half_extents: Vec3,
921}
922
923impl StreamingVolumeBox {
924    pub fn contains_point(&self, world_pos: Vec3) -> bool {
925        // Transform point to local space
926        let inv = self.transform.inverse();
927        let local = inv.transform_point3(world_pos);
928        local.x.abs() <= self.half_extents.x
929            && local.y.abs() <= self.half_extents.y
930            && local.z.abs() <= self.half_extents.z
931    }
932
933    pub fn distance_to_point(&self, world_pos: Vec3) -> f32 {
934        let inv = self.transform.inverse();
935        let local = inv.transform_point3(world_pos);
936        let dx = (local.x.abs() - self.half_extents.x).max(0.0);
937        let dy = (local.y.abs() - self.half_extents.y).max(0.0);
938        let dz = (local.z.abs() - self.half_extents.z).max(0.0);
939        (dx * dx + dy * dy + dz * dz).sqrt()
940    }
941
942    pub fn world_aabb(&self) -> Aabb {
943        // Transform all 8 corners
944        let e = self.half_extents;
945        let corners = [
946            Vec3::new(-e.x, -e.y, -e.z),
947            Vec3::new( e.x, -e.y, -e.z),
948            Vec3::new(-e.x,  e.y, -e.z),
949            Vec3::new( e.x,  e.y, -e.z),
950            Vec3::new(-e.x, -e.y,  e.z),
951            Vec3::new( e.x, -e.y,  e.z),
952            Vec3::new(-e.x,  e.y,  e.z),
953            Vec3::new( e.x,  e.y,  e.z),
954        ];
955        let mut min = Vec3::splat(f32::MAX);
956        let mut max = Vec3::splat(f32::MIN);
957        for c in corners {
958            let w = self.transform.transform_point3(c);
959            min = min.min(w);
960            max = max.max(w);
961        }
962        Aabb::new(min, max)
963    }
964}
965
966#[derive(Debug, Clone)]
967pub struct StreamingVolume {
968    pub id: u64,
969    pub name: String,
970    pub shape: VolumeShape,
971    pub box_volume: Option<StreamingVolumeBox>,
972    pub sphere_volume: Option<Sphere>,
973    pub convex_hull: Option<ConvexHull>,
974    pub load_radius: f32,
975    pub unload_radius: f32,
976    pub importance_weight: f32,
977    pub target_level_ids: Vec<u64>,
978    pub is_enabled: bool,
979}
980
981impl StreamingVolume {
982    pub fn new_box(id: u64, name: String, transform: Mat4, half_extents: Vec3) -> Self {
983        Self {
984            id,
985            name,
986            shape: VolumeShape::Box,
987            box_volume: Some(StreamingVolumeBox { transform, half_extents }),
988            sphere_volume: None,
989            convex_hull: None,
990            load_radius: 0.0,
991            unload_radius: 0.0,
992            importance_weight: 1.0,
993            target_level_ids: Vec::new(),
994            is_enabled: true,
995        }
996    }
997
998    pub fn new_sphere(id: u64, name: String, center: Vec3, load_radius: f32, unload_radius: f32) -> Self {
999        Self {
1000            id,
1001            name,
1002            shape: VolumeShape::Sphere,
1003            box_volume: None,
1004            sphere_volume: Some(Sphere::new(center, load_radius)),
1005            convex_hull: None,
1006            load_radius,
1007            unload_radius,
1008            importance_weight: 1.0,
1009            target_level_ids: Vec::new(),
1010            is_enabled: true,
1011        }
1012    }
1013
1014    pub fn contains_point(&self, p: Vec3) -> bool {
1015        if !self.is_enabled { return false; }
1016        match self.shape {
1017            VolumeShape::Box => {
1018                self.box_volume.as_ref().map_or(false, |b| b.contains_point(p))
1019            }
1020            VolumeShape::Sphere => {
1021                self.sphere_volume.as_ref().map_or(false, |s| s.contains_point(p))
1022            }
1023            VolumeShape::ConvexHull => {
1024                self.convex_hull.as_ref().map_or(false, |c| c.contains_point(p))
1025            }
1026            VolumeShape::Cylinder => {
1027                // Use sphere as fallback
1028                self.sphere_volume.as_ref().map_or(false, |s| {
1029                    let flat = Vec3::new(p.x - s.center.x, 0.0, p.z - s.center.z);
1030                    flat.length_squared() <= s.radius * s.radius
1031                        && (p.y - s.center.y).abs() <= s.radius
1032                })
1033            }
1034        }
1035    }
1036
1037    pub fn distance_to_point(&self, p: Vec3) -> f32 {
1038        match self.shape {
1039            VolumeShape::Box => {
1040                self.box_volume.as_ref().map_or(f32::MAX, |b| b.distance_to_point(p))
1041            }
1042            VolumeShape::Sphere | VolumeShape::Cylinder => {
1043                self.sphere_volume.as_ref().map_or(f32::MAX, |s| {
1044                    ((s.center - p).length() - s.radius).max(0.0)
1045                })
1046            }
1047            VolumeShape::ConvexHull => {
1048                // Approximate with AABB
1049                if let Some(ch) = &self.convex_hull {
1050                    if ch.contains_point(p) { 0.0 } else { 1.0 } // simplified
1051                } else { f32::MAX }
1052            }
1053        }
1054    }
1055
1056    pub fn should_trigger_load(&self, p: Vec3) -> bool {
1057        match self.shape {
1058            VolumeShape::Sphere => {
1059                self.sphere_volume.as_ref().map_or(false, |s| {
1060                    (s.center - p).length() < self.load_radius
1061                })
1062            }
1063            _ => self.contains_point(p),
1064        }
1065    }
1066
1067    pub fn should_trigger_unload(&self, p: Vec3) -> bool {
1068        match self.shape {
1069            VolumeShape::Sphere => {
1070                self.sphere_volume.as_ref().map_or(false, |s| {
1071                    (s.center - p).length() > self.unload_radius
1072                })
1073            }
1074            _ => !self.contains_point(p),
1075        }
1076    }
1077}
1078
1079// ============================================================
1080// ASYNC LOADING QUEUE
1081// ============================================================
1082
1083#[derive(Debug, Clone)]
1084pub struct LoadRequest {
1085    pub level_id: u64,
1086    pub priority: LoadPriority,
1087    pub distance_weight: f32,
1088    pub enqueue_time_ms: f64,
1089    pub predicted_load_time_ms: f32,
1090    pub is_prefetch: bool,
1091}
1092
1093impl LoadRequest {
1094    pub fn score(&self) -> f32 {
1095        let priority_bonus = match self.priority {
1096            LoadPriority::Critical => 1000.0,
1097            LoadPriority::High => 100.0,
1098            LoadPriority::Medium => 10.0,
1099            LoadPriority::Low => 1.0,
1100            LoadPriority::Prefetch => 0.1,
1101        };
1102        priority_bonus + self.distance_weight * 10.0
1103    }
1104}
1105
1106#[derive(Debug)]
1107pub struct StreamingLoadQueue {
1108    pub pending: Vec<LoadRequest>,
1109    pub in_flight: Vec<LoadRequest>,
1110    pub max_concurrent: usize,
1111    pub total_bytes_loaded: u64,
1112    pub total_loads: u64,
1113    pub bandwidth_samples: VecDeque<f32>, // MB/s
1114}
1115
1116impl StreamingLoadQueue {
1117    pub fn new(max_concurrent: usize) -> Self {
1118        Self {
1119            pending: Vec::new(),
1120            in_flight: Vec::new(),
1121            max_concurrent,
1122            total_bytes_loaded: 0,
1123            total_loads: 0,
1124            bandwidth_samples: VecDeque::with_capacity(BANDWIDTH_ESTIMATE_WINDOW),
1125        }
1126    }
1127
1128    pub fn enqueue(&mut self, request: LoadRequest) {
1129        // Remove duplicates
1130        self.pending.retain(|r| r.level_id != request.level_id);
1131        self.pending.push(request);
1132        // Sort by score descending
1133        self.pending.sort_by(|a, b| b.score().partial_cmp(&a.score()).unwrap_or(std::cmp::Ordering::Equal));
1134    }
1135
1136    pub fn dequeue_next(&mut self) -> Option<LoadRequest> {
1137        if self.in_flight.len() >= self.max_concurrent { return None; }
1138        if self.pending.is_empty() { return None; }
1139        let req = self.pending.remove(0);
1140        self.in_flight.push(req.clone());
1141        Some(req)
1142    }
1143
1144    pub fn complete_load(&mut self, level_id: u64, bytes_loaded: u64, time_ms: f32) {
1145        self.in_flight.retain(|r| r.level_id != level_id);
1146        self.total_bytes_loaded += bytes_loaded;
1147        self.total_loads += 1;
1148        if time_ms > 0.0 {
1149            let mb_per_s = (bytes_loaded as f32 / (1024.0 * 1024.0)) / (time_ms / 1000.0);
1150            if self.bandwidth_samples.len() >= BANDWIDTH_ESTIMATE_WINDOW {
1151                self.bandwidth_samples.pop_front();
1152            }
1153            self.bandwidth_samples.push_back(mb_per_s);
1154        }
1155    }
1156
1157    pub fn cancel_load(&mut self, level_id: u64) {
1158        self.pending.retain(|r| r.level_id != level_id);
1159        self.in_flight.retain(|r| r.level_id != level_id);
1160    }
1161
1162    pub fn estimated_bandwidth_mb_s(&self) -> f32 {
1163        if self.bandwidth_samples.is_empty() { return 100.0; }
1164        let sum: f32 = self.bandwidth_samples.iter().sum();
1165        sum / self.bandwidth_samples.len() as f32
1166    }
1167
1168    pub fn estimated_remaining_time_ms(&self) -> f32 {
1169        let bandwidth = self.estimated_bandwidth_mb_s();
1170        let total_pending_mb: f32 = self.pending.iter()
1171            .map(|r| r.predicted_load_time_ms)
1172            .sum::<f32>() / 1000.0; // rough
1173        if bandwidth > 0.0 { total_pending_mb / bandwidth * 1000.0 } else { f32::MAX }
1174    }
1175
1176    pub fn is_loading(&self, level_id: u64) -> bool {
1177        self.in_flight.iter().any(|r| r.level_id == level_id)
1178    }
1179
1180    pub fn is_pending(&self, level_id: u64) -> bool {
1181        self.pending.iter().any(|r| r.level_id == level_id)
1182    }
1183
1184    pub fn queue_depth(&self) -> usize {
1185        self.pending.len() + self.in_flight.len()
1186    }
1187}
1188
1189// ============================================================
1190// PREFETCH PREDICTOR
1191// ============================================================
1192
1193#[derive(Debug, Clone)]
1194pub struct CameraVelocityTracker {
1195    pub positions: VecDeque<Vec3>,
1196    pub timestamps: VecDeque<f64>,
1197    pub max_samples: usize,
1198}
1199
1200impl CameraVelocityTracker {
1201    pub fn new(max_samples: usize) -> Self {
1202        Self {
1203            positions: VecDeque::with_capacity(max_samples),
1204            timestamps: VecDeque::with_capacity(max_samples),
1205            max_samples,
1206        }
1207    }
1208
1209    pub fn add_sample(&mut self, pos: Vec3, time_ms: f64) {
1210        if self.positions.len() >= self.max_samples {
1211            self.positions.pop_front();
1212            self.timestamps.pop_front();
1213        }
1214        self.positions.push_back(pos);
1215        self.timestamps.push_back(time_ms);
1216    }
1217
1218    pub fn velocity(&self) -> Vec3 {
1219        let n = self.positions.len();
1220        if n < 2 { return Vec3::ZERO; }
1221        let dt = (self.timestamps[n - 1] - self.timestamps[0]) / 1000.0; // seconds
1222        if dt < 1e-6 { return Vec3::ZERO; }
1223        let dp = self.positions[n - 1] - self.positions[0];
1224        dp / dt as f32
1225    }
1226
1227    pub fn acceleration(&self) -> Vec3 {
1228        let n = self.positions.len();
1229        if n < 3 { return Vec3::ZERO; }
1230        let mid = n / 2;
1231        let dt1 = ((self.timestamps[mid] - self.timestamps[0]) / 1000.0) as f32;
1232        let dt2 = ((self.timestamps[n - 1] - self.timestamps[mid]) / 1000.0) as f32;
1233        if dt1 < 1e-6 || dt2 < 1e-6 { return Vec3::ZERO; }
1234        let v1 = (self.positions[mid] - self.positions[0]) / dt1;
1235        let v2 = (self.positions[n - 1] - self.positions[mid]) / dt2;
1236        let dt = (dt1 + dt2) * 0.5;
1237        (v2 - v1) / dt
1238    }
1239
1240    pub fn predict_position(&self, lookahead_s: f32) -> Vec3 {
1241        if self.positions.is_empty() { return Vec3::ZERO; }
1242        let current = *self.positions.back().unwrap();
1243        let vel = self.velocity();
1244        let acc = self.acceleration();
1245        // Kinematic prediction: p + v*t + 0.5*a*t^2
1246        current + vel * lookahead_s + acc * 0.5 * lookahead_s * lookahead_s
1247    }
1248}
1249
1250#[derive(Debug)]
1251pub struct PrefetchPredictor {
1252    pub camera_tracker: CameraVelocityTracker,
1253    pub lookahead_seconds: f32,
1254    pub prefetch_budget_ratio: f32, // fraction of memory budget for prefetch
1255}
1256
1257impl PrefetchPredictor {
1258    pub fn new(lookahead_seconds: f32) -> Self {
1259        Self {
1260            camera_tracker: CameraVelocityTracker::new(30),
1261            lookahead_seconds,
1262            prefetch_budget_ratio: 0.2,
1263        }
1264    }
1265
1266    pub fn update(&mut self, camera_pos: Vec3, time_ms: f64) {
1267        self.camera_tracker.add_sample(camera_pos, time_ms);
1268    }
1269
1270    pub fn predicted_camera_pos(&self) -> Vec3 {
1271        self.camera_tracker.predict_position(self.lookahead_seconds)
1272    }
1273
1274    pub fn get_prefetch_candidates<'a>(
1275        &self,
1276        levels: &'a [StreamingLevel],
1277        current_pos: Vec3,
1278    ) -> Vec<u64> {
1279        let predicted = self.predicted_camera_pos();
1280        let mut candidates = Vec::new();
1281        for level in levels {
1282            if level.state != StreamingState::Unloaded { continue; }
1283            // Check if predicted position would require this level
1284            if level.bounds.distance_to_point(predicted) < level.load_distance {
1285                // Not needed from current pos
1286                if level.bounds.distance_to_point(current_pos) >= level.load_distance {
1287                    candidates.push(level.id);
1288                }
1289            }
1290        }
1291        candidates
1292    }
1293}
1294
1295// ============================================================
1296// MEMORY PRESSURE MANAGER
1297// ============================================================
1298
1299#[derive(Debug)]
1300pub struct MemoryPressureManager {
1301    pub budget_mb: f32,
1302    pub used_mb: f32,
1303    pub eviction_policy: EvictionPolicy,
1304    pub lru_order: VecDeque<u64>, // level_ids in LRU order
1305    pub access_counts: HashMap<u64, u64>,
1306    pub pressure_threshold: f32,
1307    pub critical_threshold: f32,
1308}
1309
1310impl MemoryPressureManager {
1311    pub fn new(budget_mb: f32) -> Self {
1312        Self {
1313            budget_mb,
1314            used_mb: 0.0,
1315            eviction_policy: EvictionPolicy::Lru,
1316            lru_order: VecDeque::new(),
1317            access_counts: HashMap::new(),
1318            pressure_threshold: 0.8,
1319            critical_threshold: 0.95,
1320        }
1321    }
1322
1323    pub fn is_under_pressure(&self) -> bool {
1324        self.used_mb / self.budget_mb > self.pressure_threshold
1325    }
1326
1327    pub fn is_critical(&self) -> bool {
1328        self.used_mb / self.budget_mb > self.critical_threshold
1329    }
1330
1331    pub fn available_mb(&self) -> f32 {
1332        (self.budget_mb - self.used_mb).max(0.0)
1333    }
1334
1335    pub fn can_load(&self, size_mb: f32) -> bool {
1336        self.used_mb + size_mb <= self.budget_mb
1337    }
1338
1339    pub fn record_access(&mut self, level_id: u64) {
1340        self.lru_order.retain(|&id| id != level_id);
1341        self.lru_order.push_back(level_id);
1342        *self.access_counts.entry(level_id).or_insert(0) += 1;
1343    }
1344
1345    pub fn record_load(&mut self, level_id: u64, size_mb: f32) {
1346        self.used_mb += size_mb;
1347        self.record_access(level_id);
1348    }
1349
1350    pub fn record_unload(&mut self, level_id: u64, size_mb: f32) {
1351        self.used_mb = (self.used_mb - size_mb).max(0.0);
1352        self.lru_order.retain(|&id| id != level_id);
1353        self.access_counts.remove(&level_id);
1354    }
1355
1356    pub fn select_eviction_candidates(&self, needed_mb: f32, levels: &[StreamingLevel]) -> Vec<u64> {
1357        let mut candidates: Vec<u64> = Vec::new();
1358        let mut freed = 0.0f32;
1359
1360        let sortable_levels: Vec<&StreamingLevel> = levels.iter()
1361            .filter(|l| l.state == StreamingState::Loaded && l.persistence != LevelPersistence::AlwaysLoaded)
1362            .collect();
1363
1364        let mut order: Vec<usize> = (0..sortable_levels.len()).collect();
1365
1366        match self.eviction_policy {
1367            EvictionPolicy::Lru => {
1368                // Sort by LRU position (front = least recently used)
1369                order.sort_by_key(|&i| {
1370                    self.lru_order.iter().position(|&id| id == sortable_levels[i].id)
1371                        .unwrap_or(0)
1372                });
1373            }
1374            EvictionPolicy::Lfu => {
1375                order.sort_by_key(|&i| {
1376                    self.access_counts.get(&sortable_levels[i].id).copied().unwrap_or(0)
1377                });
1378            }
1379            EvictionPolicy::Distance => {
1380                order.sort_by(|&a, &b| {
1381                    sortable_levels[b].distance_to_camera
1382                        .partial_cmp(&sortable_levels[a].distance_to_camera)
1383                        .unwrap_or(std::cmp::Ordering::Equal)
1384                });
1385            }
1386            EvictionPolicy::Priority => {
1387                order.sort_by_key(|&i| sortable_levels[i].priority as u8);
1388            }
1389        }
1390
1391        for i in order {
1392            if freed >= needed_mb { break; }
1393            let lvl = sortable_levels[i];
1394            candidates.push(lvl.id);
1395            freed += lvl.memory_footprint_mb;
1396        }
1397
1398        candidates
1399    }
1400
1401    pub fn pressure_ratio(&self) -> f32 {
1402        if self.budget_mb <= 0.0 { return 1.0; }
1403        (self.used_mb / self.budget_mb).clamp(0.0, 1.0)
1404    }
1405}
1406
1407// ============================================================
1408// DEPENDENCY GRAPH
1409// ============================================================
1410
1411#[derive(Debug, Clone)]
1412pub struct DependencyNode {
1413    pub level_id: u64,
1414    pub dependencies: Vec<u64>,
1415    pub dependents: Vec<u64>,
1416    pub edge_types: HashMap<u64, DependencyEdgeType>,
1417}
1418
1419impl DependencyNode {
1420    pub fn new(level_id: u64) -> Self {
1421        Self {
1422            level_id,
1423            dependencies: Vec::new(),
1424            dependents: Vec::new(),
1425            edge_types: HashMap::new(),
1426        }
1427    }
1428
1429    pub fn add_dependency(&mut self, dep_id: u64, edge_type: DependencyEdgeType) {
1430        if !self.dependencies.contains(&dep_id) {
1431            self.dependencies.push(dep_id);
1432            self.edge_types.insert(dep_id, edge_type);
1433        }
1434    }
1435}
1436
1437#[derive(Debug)]
1438pub struct DependencyGraph {
1439    pub nodes: HashMap<u64, DependencyNode>,
1440}
1441
1442impl DependencyGraph {
1443    pub fn new() -> Self {
1444        Self { nodes: HashMap::new() }
1445    }
1446
1447    pub fn add_level(&mut self, level_id: u64) {
1448        self.nodes.entry(level_id).or_insert_with(|| DependencyNode::new(level_id));
1449    }
1450
1451    pub fn add_dependency(&mut self, from: u64, to: u64, edge_type: DependencyEdgeType) {
1452        self.add_level(from);
1453        self.add_level(to);
1454        if let Some(node) = self.nodes.get_mut(&from) {
1455            node.add_dependency(to, edge_type);
1456        }
1457        if let Some(node) = self.nodes.get_mut(&to) {
1458            if !node.dependents.contains(&from) {
1459                node.dependents.push(from);
1460            }
1461        }
1462    }
1463
1464    /// DFS-based cycle detection. Returns Some(cycle) if a cycle exists.
1465    pub fn detect_cycles(&self) -> Option<Vec<u64>> {
1466        let mut visited: HashSet<u64> = HashSet::new();
1467        let mut rec_stack: HashSet<u64> = HashSet::new();
1468        let mut path: Vec<u64> = Vec::new();
1469
1470        for &start_id in self.nodes.keys() {
1471            if !visited.contains(&start_id) {
1472                if let Some(cycle) = self.dfs_cycle_detect(start_id, &mut visited, &mut rec_stack, &mut path) {
1473                    return Some(cycle);
1474                }
1475            }
1476        }
1477        None
1478    }
1479
1480    fn dfs_cycle_detect(
1481        &self,
1482        node_id: u64,
1483        visited: &mut HashSet<u64>,
1484        rec_stack: &mut HashSet<u64>,
1485        path: &mut Vec<u64>,
1486    ) -> Option<Vec<u64>> {
1487        visited.insert(node_id);
1488        rec_stack.insert(node_id);
1489        path.push(node_id);
1490
1491        if let Some(node) = self.nodes.get(&node_id) {
1492            for &dep in &node.dependencies {
1493                let edge = node.edge_types.get(&dep).copied().unwrap_or(DependencyEdgeType::HardDependency);
1494                if edge == DependencyEdgeType::Optional { continue; }
1495
1496                if !visited.contains(&dep) {
1497                    if let Some(cycle) = self.dfs_cycle_detect(dep, visited, rec_stack, path) {
1498                        return Some(cycle);
1499                    }
1500                } else if rec_stack.contains(&dep) {
1501                    // Found cycle - extract it
1502                    if let Some(start) = path.iter().position(|&id| id == dep) {
1503                        return Some(path[start..].to_vec());
1504                    }
1505                    return Some(vec![dep]);
1506                }
1507            }
1508        }
1509
1510        path.pop();
1511        rec_stack.remove(&node_id);
1512        None
1513    }
1514
1515    /// Topological sort (Kahn's algorithm). Returns load order.
1516    pub fn topological_sort(&self) -> Result<Vec<u64>, Vec<u64>> {
1517        let mut in_degree: HashMap<u64, usize> = HashMap::new();
1518        for &id in self.nodes.keys() {
1519            in_degree.insert(id, 0);
1520        }
1521        for node in self.nodes.values() {
1522            for &dep in &node.dependencies {
1523                let edge = node.edge_types.get(&dep).copied().unwrap_or(DependencyEdgeType::HardDependency);
1524                if edge != DependencyEdgeType::Optional {
1525                    *in_degree.entry(dep).or_insert(0) += 0; // ensure dep exists
1526                    *in_degree.entry(node.level_id).or_insert(0) += 1;
1527                }
1528            }
1529        }
1530
1531        let mut queue: VecDeque<u64> = in_degree.iter()
1532            .filter(|(_, &d)| d == 0)
1533            .map(|(&id, _)| id)
1534            .collect();
1535        let mut order = Vec::new();
1536
1537        while let Some(id) = queue.pop_front() {
1538            order.push(id);
1539            if let Some(node) = self.nodes.get(&id) {
1540                for &dependent in &node.dependents {
1541                    if let Some(d) = in_degree.get_mut(&dependent) {
1542                        *d = d.saturating_sub(1);
1543                        if *d == 0 {
1544                            queue.push_back(dependent);
1545                        }
1546                    }
1547                }
1548            }
1549        }
1550
1551        if order.len() != self.nodes.len() {
1552            // Cycle detected — return remaining nodes
1553            let remaining: Vec<u64> = in_degree.iter()
1554                .filter(|(_, &d)| d > 0)
1555                .map(|(&id, _)| id)
1556                .collect();
1557            Err(remaining)
1558        } else {
1559            Ok(order)
1560        }
1561    }
1562
1563    /// Plan parallel load batches based on dependency order
1564    pub fn parallel_load_plan(&self) -> Vec<Vec<u64>> {
1565        let mut batches: Vec<Vec<u64>> = Vec::new();
1566        match self.topological_sort() {
1567            Ok(order) => {
1568                let mut loaded: HashSet<u64> = HashSet::new();
1569                let mut remaining: Vec<u64> = order;
1570                while !remaining.is_empty() {
1571                    let mut batch: Vec<u64> = Vec::new();
1572                    let mut next_remaining: Vec<u64> = Vec::new();
1573                    for id in remaining {
1574                        let can_load = if let Some(node) = self.nodes.get(&id) {
1575                            node.dependencies.iter().all(|dep| {
1576                                let edge = node.edge_types.get(dep).copied()
1577                                    .unwrap_or(DependencyEdgeType::HardDependency);
1578                                edge == DependencyEdgeType::Optional || loaded.contains(dep)
1579                            })
1580                        } else { true };
1581                        if can_load {
1582                            batch.push(id);
1583                        } else {
1584                            next_remaining.push(id);
1585                        }
1586                    }
1587                    if batch.is_empty() { break; } // Safety against infinite loop
1588                    for &id in &batch { loaded.insert(id); }
1589                    batches.push(batch);
1590                    remaining = next_remaining;
1591                }
1592            }
1593            Err(_) => {
1594                // Fallback: load everything in one batch
1595                batches.push(self.nodes.keys().copied().collect());
1596            }
1597        }
1598        batches
1599    }
1600
1601    pub fn get_all_dependencies(&self, level_id: u64, include_optional: bool) -> HashSet<u64> {
1602        let mut result = HashSet::new();
1603        let mut stack = vec![level_id];
1604        while let Some(id) = stack.pop() {
1605            if result.contains(&id) { continue; }
1606            result.insert(id);
1607            if let Some(node) = self.nodes.get(&id) {
1608                for &dep in &node.dependencies {
1609                    let edge = node.edge_types.get(&dep).copied()
1610                        .unwrap_or(DependencyEdgeType::HardDependency);
1611                    if include_optional || edge != DependencyEdgeType::Optional {
1612                        stack.push(dep);
1613                    }
1614                }
1615            }
1616        }
1617        result.remove(&level_id);
1618        result
1619    }
1620}
1621
1622// ============================================================
1623// PORTAL SYSTEM
1624// ============================================================
1625
1626#[derive(Debug, Clone)]
1627pub struct Portal {
1628    pub id: u64,
1629    pub sector_a: u64,
1630    pub sector_b: u64,
1631    pub center: Vec3,
1632    pub normal: Vec3,
1633    pub half_extents: Vec2,
1634    pub is_open: bool,
1635    pub transmission: f32, // 0-1, visibility through portal
1636}
1637
1638impl Portal {
1639    pub fn new(id: u64, sector_a: u64, sector_b: u64, center: Vec3, normal: Vec3, half_extents: Vec2) -> Self {
1640        Self {
1641            id,
1642            sector_a,
1643            sector_b,
1644            center,
1645            normal: normal.normalize_or_zero(),
1646            half_extents,
1647            is_open: true,
1648            transmission: 1.0,
1649        }
1650    }
1651
1652    pub fn is_visible_from(&self, camera_pos: Vec3) -> bool {
1653        if !self.is_open { return false; }
1654        let to_portal = (self.center - camera_pos).normalize_or_zero();
1655        // Visible if camera is on positive side of portal
1656        self.normal.dot(to_portal) < 0.0
1657    }
1658
1659    pub fn project_to_clip(&self, view_proj: &Mat4) -> Option<[Vec2; 4]> {
1660        let right = Vec3::new(self.normal.z, 0.0, -self.normal.x).normalize_or_zero();
1661        let up = right.cross(self.normal).normalize_or_zero();
1662        let corners = [
1663            self.center + right * self.half_extents.x + up * self.half_extents.y,
1664            self.center - right * self.half_extents.x + up * self.half_extents.y,
1665            self.center - right * self.half_extents.x - up * self.half_extents.y,
1666            self.center + right * self.half_extents.x - up * self.half_extents.y,
1667        ];
1668        let mut result = [[0.0f32; 2]; 4];
1669        for (i, &c) in corners.iter().enumerate() {
1670            let clip = *view_proj * Vec4::new(c.x, c.y, c.z, 1.0);
1671            if clip.w <= 0.0 { return None; }
1672            result[i] = [clip.x / clip.w, clip.y / clip.w];
1673        }
1674        Some(result.map(|[x, y]| Vec2::new(x, y)))
1675    }
1676}
1677
1678// ============================================================
1679// SECTOR SYSTEM
1680// ============================================================
1681
1682#[derive(Debug, Clone)]
1683pub struct Waypoint {
1684    pub id: u64,
1685    pub name: String,
1686    pub position: Vec3,
1687    pub rotation: Quat,
1688    pub tags: Vec<String>,
1689    pub is_spawn_point: bool,
1690    pub spawn_radius: f32,
1691}
1692
1693impl Waypoint {
1694    pub fn new(id: u64, name: String, position: Vec3) -> Self {
1695        Self {
1696            id,
1697            name,
1698            position,
1699            rotation: Quat::IDENTITY,
1700            tags: Vec::new(),
1701            is_spawn_point: false,
1702            spawn_radius: 1.0,
1703        }
1704    }
1705}
1706
1707#[derive(Debug, Clone)]
1708pub struct Sector {
1709    pub id: u64,
1710    pub name: String,
1711    pub bounds: Aabb,
1712    pub portals: Vec<u64>,          // Portal IDs
1713    pub adjacent_sectors: Vec<u64>, // Sector IDs
1714    pub waypoints: Vec<Waypoint>,
1715    pub level_ids: Vec<u64>,
1716    pub ai_spawn_budget: u32,
1717    pub is_interior: bool,
1718    pub ambient_sound_id: Option<u64>,
1719    pub reverb_preset: u32,
1720    pub transition_type: SectorTransitionType,
1721}
1722
1723impl Sector {
1724    pub fn new(id: u64, name: String, bounds: Aabb) -> Self {
1725        Self {
1726            id,
1727            name,
1728            bounds,
1729            portals: Vec::new(),
1730            adjacent_sectors: Vec::new(),
1731            waypoints: Vec::new(),
1732            level_ids: Vec::new(),
1733            ai_spawn_budget: 10,
1734            is_interior: false,
1735            ambient_sound_id: None,
1736            reverb_preset: 0,
1737            transition_type: SectorTransitionType::Fade,
1738        }
1739    }
1740
1741    pub fn contains_point(&self, p: Vec3) -> bool {
1742        self.bounds.contains_point(p)
1743    }
1744
1745    pub fn nearest_waypoint(&self, pos: Vec3) -> Option<&Waypoint> {
1746        self.waypoints.iter().min_by(|a, b| {
1747            let da = (a.position - pos).length_squared();
1748            let db = (b.position - pos).length_squared();
1749            da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
1750        })
1751    }
1752
1753    pub fn spawn_waypoints(&self) -> Vec<&Waypoint> {
1754        self.waypoints.iter().filter(|w| w.is_spawn_point).collect()
1755    }
1756}
1757
1758#[derive(Debug)]
1759pub struct SectorGraph {
1760    pub sectors: HashMap<u64, Sector>,
1761    pub portals: HashMap<u64, Portal>,
1762    pub current_sector: Option<u64>,
1763}
1764
1765impl SectorGraph {
1766    pub fn new() -> Self {
1767        Self {
1768            sectors: HashMap::new(),
1769            portals: HashMap::new(),
1770            current_sector: None,
1771        }
1772    }
1773
1774    pub fn add_sector(&mut self, sector: Sector) {
1775        self.sectors.insert(sector.id, sector);
1776    }
1777
1778    pub fn add_portal(&mut self, portal: Portal) {
1779        let id = portal.id;
1780        let sa = portal.sector_a;
1781        let sb = portal.sector_b;
1782        self.portals.insert(id, portal);
1783        if let Some(s) = self.sectors.get_mut(&sa) {
1784            if !s.portals.contains(&id) { s.portals.push(id); }
1785            if !s.adjacent_sectors.contains(&sb) { s.adjacent_sectors.push(sb); }
1786        }
1787        if let Some(s) = self.sectors.get_mut(&sb) {
1788            if !s.portals.contains(&id) { s.portals.push(id); }
1789            if !s.adjacent_sectors.contains(&sa) { s.adjacent_sectors.push(sa); }
1790        }
1791    }
1792
1793    pub fn find_sector_at(&self, pos: Vec3) -> Option<u64> {
1794        for (id, sector) in &self.sectors {
1795            if sector.contains_point(pos) {
1796                return Some(*id);
1797            }
1798        }
1799        None
1800    }
1801
1802    /// BFS to find potentially visible sectors through portals from a camera position
1803    pub fn compute_pvs(&self, camera_pos: Vec3, view_proj: &Mat4, max_depth: usize) -> HashSet<u64> {
1804        let start = match self.find_sector_at(camera_pos) {
1805            Some(id) => id,
1806            None => return HashSet::new(),
1807        };
1808
1809        let mut visible = HashSet::new();
1810        visible.insert(start);
1811        let mut queue: VecDeque<(u64, usize)> = VecDeque::new();
1812        queue.push_back((start, 0));
1813
1814        while let Some((sector_id, depth)) = queue.pop_front() {
1815            if depth >= max_depth { continue; }
1816            let portal_ids = if let Some(s) = self.sectors.get(&sector_id) {
1817                s.portals.clone()
1818            } else { continue };
1819
1820            for portal_id in portal_ids {
1821                if let Some(portal) = self.portals.get(&portal_id) {
1822                    if !portal.is_open { continue; }
1823                    if !portal.is_visible_from(camera_pos) { continue; }
1824                    let next = if portal.sector_a == sector_id { portal.sector_b } else { portal.sector_a };
1825                    if visible.insert(next) {
1826                        queue.push_back((next, depth + 1));
1827                    }
1828                }
1829            }
1830        }
1831
1832        visible
1833    }
1834
1835    pub fn get_adjacent_level_ids(&self, sector_id: u64) -> Vec<u64> {
1836        let mut result = Vec::new();
1837        if let Some(sector) = self.sectors.get(&sector_id) {
1838            for &level_id in &sector.level_ids {
1839                result.push(level_id);
1840            }
1841            for &adj_id in &sector.adjacent_sectors {
1842                if let Some(adj) = self.sectors.get(&adj_id) {
1843                    for &level_id in &adj.level_ids {
1844                        if !result.contains(&level_id) {
1845                            result.push(level_id);
1846                        }
1847                    }
1848                }
1849            }
1850        }
1851        result
1852    }
1853}
1854
1855// ============================================================
1856// STREAMING EVENT TIMELINE
1857// ============================================================
1858
1859#[derive(Debug, Clone)]
1860pub struct StreamingEvent {
1861    pub timestamp_ms: f64,
1862    pub kind: StreamingEventKind,
1863    pub level_id: u64,
1864    pub level_name: String,
1865    pub data_mb: f32,
1866    pub duration_ms: f32,
1867    pub camera_pos: Vec3,
1868}
1869
1870#[derive(Debug)]
1871pub struct StreamingTimeline {
1872    pub events: VecDeque<StreamingEvent>,
1873    pub capacity: usize,
1874    pub start_time_ms: f64,
1875    pub bandwidth_history: VecDeque<(f64, f32)>, // (time, MB/s)
1876    pub memory_history: VecDeque<(f64, f32)>,    // (time, MB used)
1877}
1878
1879impl StreamingTimeline {
1880    pub fn new(capacity: usize) -> Self {
1881        Self {
1882            events: VecDeque::with_capacity(capacity),
1883            capacity,
1884            start_time_ms: 0.0,
1885            bandwidth_history: VecDeque::with_capacity(capacity),
1886            memory_history: VecDeque::with_capacity(capacity),
1887        }
1888    }
1889
1890    pub fn record(&mut self, event: StreamingEvent) {
1891        if self.events.len() >= self.capacity {
1892            self.events.pop_front();
1893        }
1894        self.events.push_back(event);
1895    }
1896
1897    pub fn record_bandwidth(&mut self, time_ms: f64, mb_per_s: f32) {
1898        if self.bandwidth_history.len() >= self.capacity {
1899            self.bandwidth_history.pop_front();
1900        }
1901        self.bandwidth_history.push_back((time_ms, mb_per_s));
1902    }
1903
1904    pub fn record_memory(&mut self, time_ms: f64, mb_used: f32) {
1905        if self.memory_history.len() >= self.capacity {
1906            self.memory_history.pop_front();
1907        }
1908        self.memory_history.push_back((time_ms, mb_used));
1909    }
1910
1911    pub fn events_in_range(&self, start_ms: f64, end_ms: f64) -> Vec<&StreamingEvent> {
1912        self.events.iter()
1913            .filter(|e| e.timestamp_ms >= start_ms && e.timestamp_ms <= end_ms)
1914            .collect()
1915    }
1916
1917    pub fn load_events(&self) -> Vec<&StreamingEvent> {
1918        self.events.iter()
1919            .filter(|e| e.kind == StreamingEventKind::LevelLoadCompleted)
1920            .collect()
1921    }
1922
1923    pub fn unload_events(&self) -> Vec<&StreamingEvent> {
1924        self.events.iter()
1925            .filter(|e| e.kind == StreamingEventKind::LevelUnloadCompleted)
1926            .collect()
1927    }
1928
1929    pub fn total_data_loaded_mb(&self) -> f32 {
1930        self.load_events().iter().map(|e| e.data_mb).sum()
1931    }
1932
1933    pub fn average_load_time_ms(&self) -> f32 {
1934        let loads = self.load_events();
1935        if loads.is_empty() { return 0.0; }
1936        let total: f32 = loads.iter().map(|e| e.duration_ms).sum();
1937        total / loads.len() as f32
1938    }
1939}
1940
1941// ============================================================
1942// LOD + STREAMING BUDGET INTEGRATOR
1943// ============================================================
1944
1945#[derive(Debug, Clone)]
1946pub struct LodBudgetEntry {
1947    pub level_id: u64,
1948    pub lod_memory_mb: f32,       // memory at current LOD
1949    pub streaming_memory_mb: f32, // streaming overhead
1950    pub lod_level: LodLevel,
1951    pub lod_bias_contribution: f32,
1952}
1953
1954#[derive(Debug)]
1955pub struct CombinedBudgetManager {
1956    pub total_budget_mb: f32,
1957    pub lod_budget_mb: f32,
1958    pub streaming_budget_mb: f32,
1959    pub entries: HashMap<u64, LodBudgetEntry>,
1960}
1961
1962impl CombinedBudgetManager {
1963    pub fn new(total_budget_mb: f32) -> Self {
1964        Self {
1965            total_budget_mb,
1966            lod_budget_mb: total_budget_mb * 0.6,
1967            streaming_budget_mb: total_budget_mb * 0.4,
1968            entries: HashMap::new(),
1969        }
1970    }
1971
1972    pub fn update_entry(&mut self, level_id: u64, lod: LodLevel, lod_mem: f32, stream_mem: f32) {
1973        let entry = self.entries.entry(level_id).or_insert(LodBudgetEntry {
1974            level_id,
1975            lod_memory_mb: 0.0,
1976            streaming_memory_mb: 0.0,
1977            lod_level: LodLevel::Culled,
1978            lod_bias_contribution: 0.0,
1979        });
1980        entry.lod_level = lod;
1981        entry.lod_memory_mb = lod_mem;
1982        entry.streaming_memory_mb = stream_mem;
1983    }
1984
1985    pub fn total_lod_usage_mb(&self) -> f32 {
1986        self.entries.values().map(|e| e.lod_memory_mb).sum()
1987    }
1988
1989    pub fn total_streaming_usage_mb(&self) -> f32 {
1990        self.entries.values().map(|e| e.streaming_memory_mb).sum()
1991    }
1992
1993    pub fn total_usage_mb(&self) -> f32 {
1994        self.total_lod_usage_mb() + self.total_streaming_usage_mb()
1995    }
1996
1997    pub fn available_mb(&self) -> f32 {
1998        (self.total_budget_mb - self.total_usage_mb()).max(0.0)
1999    }
2000
2001    pub fn compute_global_lod_bias(&self) -> f32 {
2002        let pressure = self.total_usage_mb() / self.total_budget_mb;
2003        if pressure < 0.7 { 0.0 }
2004        else if pressure < 0.9 { (pressure - 0.7) / 0.2 * 2.0 }
2005        else { 2.0 + (pressure - 0.9) / 0.1 * 4.0 }
2006    }
2007
2008    pub fn optimal_lod_for_budget(&self, level_id: u64, distance: f32) -> LodLevel {
2009        let bias = self.compute_global_lod_bias();
2010        let adjusted_dist = distance * (1.0 + bias * 0.5);
2011        if adjusted_dist < 100.0 { LodLevel::Lod0 }
2012        else if adjusted_dist < 300.0 { LodLevel::Lod1 }
2013        else if adjusted_dist < 600.0 { LodLevel::Lod2 }
2014        else if adjusted_dist < 1000.0 { LodLevel::Lod3 }
2015        else { LodLevel::Lod4 }
2016    }
2017}
2018
2019// ============================================================
2020// DEBUG VISUALIZATION
2021// ============================================================
2022
2023#[derive(Debug, Clone)]
2024pub struct DebugDrawCommand {
2025    pub kind: DebugDrawKind,
2026    pub color: Vec4,
2027    pub duration_ms: f32,
2028}
2029
2030#[derive(Debug, Clone)]
2031pub enum DebugDrawKind {
2032    Box { min: Vec3, max: Vec3 },
2033    Sphere { center: Vec3, radius: f32 },
2034    Line { start: Vec3, end: Vec3 },
2035    Text { pos: Vec3, text: String },
2036    Arrow { start: Vec3, end: Vec3 },
2037}
2038
2039#[derive(Debug)]
2040pub struct StreamingDebugVisualizer {
2041    pub overlay: DebugOverlay,
2042    pub draw_commands: Vec<DebugDrawCommand>,
2043    pub heatmap_data: HashMap<CellCoord, f32>, // 0-1 heat value
2044    pub visible_sector_ids: HashSet<u64>,
2045    pub show_load_distances: bool,
2046    pub show_memory_usage: bool,
2047    pub show_cell_grid: bool,
2048    pub max_commands: usize,
2049}
2050
2051impl StreamingDebugVisualizer {
2052    pub fn new() -> Self {
2053        Self {
2054            overlay: DebugOverlay::None,
2055            draw_commands: Vec::new(),
2056            heatmap_data: HashMap::new(),
2057            visible_sector_ids: HashSet::new(),
2058            show_load_distances: false,
2059            show_memory_usage: false,
2060            show_cell_grid: false,
2061            max_commands: 4096,
2062        }
2063    }
2064
2065    pub fn clear(&mut self) {
2066        self.draw_commands.clear();
2067    }
2068
2069    fn add_command(&mut self, cmd: DebugDrawCommand) {
2070        if self.draw_commands.len() < self.max_commands {
2071            self.draw_commands.push(cmd);
2072        }
2073    }
2074
2075    pub fn draw_level_bounds(&mut self, level: &StreamingLevel) {
2076        let color = match level.state {
2077            StreamingState::Loaded => Vec4::new(0.0, 1.0, 0.0, 0.5),
2078            StreamingState::Loading => Vec4::new(1.0, 1.0, 0.0, 0.5),
2079            StreamingState::Unloading => Vec4::new(1.0, 0.5, 0.0, 0.5),
2080            StreamingState::Queued => Vec4::new(0.0, 0.5, 1.0, 0.5),
2081            StreamingState::Unloaded => Vec4::new(0.5, 0.5, 0.5, 0.2),
2082            StreamingState::Failed => Vec4::new(1.0, 0.0, 0.0, 0.7),
2083            StreamingState::Evicted => Vec4::new(0.3, 0.0, 0.3, 0.3),
2084        };
2085        self.add_command(DebugDrawCommand {
2086            kind: DebugDrawKind::Box {
2087                min: level.bounds.min,
2088                max: level.bounds.max,
2089            },
2090            color,
2091            duration_ms: 0.0,
2092        });
2093    }
2094
2095    pub fn draw_load_distance_sphere(&mut self, level: &StreamingLevel) {
2096        if !self.show_load_distances { return; }
2097        self.add_command(DebugDrawCommand {
2098            kind: DebugDrawKind::Sphere {
2099                center: level.bounds.center(),
2100                radius: level.load_distance,
2101            },
2102            color: Vec4::new(0.0, 0.8, 0.0, 0.3),
2103            duration_ms: 0.0,
2104        });
2105        self.add_command(DebugDrawCommand {
2106            kind: DebugDrawKind::Sphere {
2107                center: level.bounds.center(),
2108                radius: level.unload_distance,
2109            },
2110            color: Vec4::new(1.0, 0.3, 0.0, 0.2),
2111            duration_ms: 0.0,
2112        });
2113    }
2114
2115    pub fn draw_cell_grid(&mut self, grid: &WorldPartitionGrid, camera_pos: Vec3) {
2116        if !self.show_cell_grid { return; }
2117        let center_cell = grid.world_to_cell(camera_pos);
2118        let r = 5i32;
2119        for dz in -r..=r {
2120            for dx in -r..=r {
2121                let coord = CellCoord::new(center_cell.x + dx, center_cell.y, center_cell.z + dz);
2122                let min = Vec3::new(
2123                    grid.origin.x + coord.x as f32 * grid.cell_size,
2124                    camera_pos.y - 1.0,
2125                    grid.origin.z + coord.z as f32 * grid.cell_size,
2126                );
2127                let max = min + Vec3::new(grid.cell_size, 2.0, grid.cell_size);
2128                let heat = self.heatmap_data.get(&coord).copied().unwrap_or(0.0);
2129                let color = Vec4::new(heat, 1.0 - heat, 0.0, 0.3);
2130                self.add_command(DebugDrawCommand {
2131                    kind: DebugDrawKind::Box { min, max },
2132                    color,
2133                    duration_ms: 0.0,
2134                });
2135            }
2136        }
2137    }
2138
2139    pub fn draw_memory_label(&mut self, level: &StreamingLevel) {
2140        if !self.show_memory_usage { return; }
2141        let text = format!("{:.1}MB / LOD{:?}", level.memory_footprint_mb, level.current_lod);
2142        self.add_command(DebugDrawCommand {
2143            kind: DebugDrawKind::Text {
2144                pos: level.bounds.center() + Vec3::Y * level.bounds.extents().y,
2145                text,
2146            },
2147            color: Vec4::new(1.0, 1.0, 1.0, 1.0),
2148            duration_ms: 0.0,
2149        });
2150    }
2151
2152    pub fn update_heatmap(&mut self, grid: &WorldPartitionGrid) {
2153        self.heatmap_data.clear();
2154        let max_mem = grid.cells.values()
2155            .map(|c| c.memory_used_mb)
2156            .fold(0.01f32, f32::max);
2157        for (coord, cell) in &grid.cells {
2158            let heat = cell.memory_used_mb / max_mem;
2159            self.heatmap_data.insert(*coord, heat.clamp(0.0, 1.0));
2160        }
2161    }
2162
2163    pub fn draw_sector_portals(&mut self, sector_graph: &SectorGraph) {
2164        for portal in sector_graph.portals.values() {
2165            let color = if portal.is_open {
2166                Vec4::new(0.0, 1.0, 1.0, 0.5)
2167            } else {
2168                Vec4::new(1.0, 0.0, 0.0, 0.5)
2169            };
2170            self.add_command(DebugDrawCommand {
2171                kind: DebugDrawKind::Sphere {
2172                    center: portal.center,
2173                    radius: 0.5,
2174                },
2175                color,
2176                duration_ms: 0.0,
2177            });
2178        }
2179    }
2180}
2181
2182// ============================================================
2183// PERSISTENT LEVEL STATE
2184// ============================================================
2185
2186#[derive(Debug, Clone)]
2187pub struct LevelSaveState {
2188    pub level_id: u64,
2189    pub is_active: bool,
2190    pub transform: Mat4,
2191    pub custom_properties: HashMap<String, f32>,
2192    pub save_timestamp: f64,
2193    pub loaded_sub_objects: Vec<u64>,
2194}
2195
2196impl LevelSaveState {
2197    pub fn new(level_id: u64) -> Self {
2198        Self {
2199            level_id,
2200            is_active: false,
2201            transform: Mat4::IDENTITY,
2202            custom_properties: HashMap::new(),
2203            save_timestamp: 0.0,
2204            loaded_sub_objects: Vec::new(),
2205        }
2206    }
2207}
2208
2209#[derive(Debug)]
2210pub struct PersistentLevelManager {
2211    pub base_level_id: u64,
2212    pub save_states: HashMap<u64, LevelSaveState>,
2213    pub transient_level_ids: HashSet<u64>,
2214    pub dynamic_level_ids: HashSet<u64>,
2215}
2216
2217impl PersistentLevelManager {
2218    pub fn new(base_level_id: u64) -> Self {
2219        Self {
2220            base_level_id,
2221            save_states: HashMap::new(),
2222            transient_level_ids: HashSet::new(),
2223            dynamic_level_ids: HashSet::new(),
2224        }
2225    }
2226
2227    pub fn save_level_state(&mut self, level: &StreamingLevel, timestamp: f64) {
2228        let state = self.save_states.entry(level.id).or_insert_with(|| LevelSaveState::new(level.id));
2229        state.is_active = level.state == StreamingState::Loaded;
2230        state.transform = level.transform;
2231        state.save_timestamp = timestamp;
2232    }
2233
2234    pub fn restore_level_state(&self, level_id: u64) -> Option<&LevelSaveState> {
2235        self.save_states.get(&level_id)
2236    }
2237
2238    pub fn mark_transient(&mut self, level_id: u64) {
2239        self.transient_level_ids.insert(level_id);
2240        self.dynamic_level_ids.remove(&level_id);
2241    }
2242
2243    pub fn mark_dynamic(&mut self, level_id: u64) {
2244        self.dynamic_level_ids.insert(level_id);
2245        self.transient_level_ids.remove(&level_id);
2246    }
2247
2248    pub fn get_levels_to_restore(&self) -> Vec<u64> {
2249        self.save_states.iter()
2250            .filter(|(id, state)| {
2251                state.is_active && !self.transient_level_ids.contains(id)
2252            })
2253            .map(|(&id, _)| id)
2254            .collect()
2255    }
2256}
2257
2258// ============================================================
2259// SIMULATION MODE
2260// ============================================================
2261
2262#[derive(Debug, Clone)]
2263pub struct SimulationCamera {
2264    pub position: Vec3,
2265    pub direction: Vec3,
2266    pub speed: f32,
2267    pub path: Vec<Vec3>,
2268    pub path_index: usize,
2269    pub loop_path: bool,
2270    pub time_accumulated_s: f32,
2271}
2272
2273impl SimulationCamera {
2274    pub fn new(position: Vec3) -> Self {
2275        Self {
2276            position,
2277            direction: Vec3::NEG_Z,
2278            speed: 10.0,
2279            path: Vec::new(),
2280            path_index: 0,
2281            loop_path: false,
2282            time_accumulated_s: 0.0,
2283        }
2284    }
2285
2286    pub fn update(&mut self, dt_s: f32) {
2287        self.time_accumulated_s += dt_s;
2288        if self.path.is_empty() { return; }
2289        let target = self.path[self.path_index];
2290        let to_target = target - self.position;
2291        let dist = to_target.length();
2292        let step = self.speed * dt_s;
2293        if dist <= step {
2294            self.position = target;
2295            self.path_index += 1;
2296            if self.path_index >= self.path.len() {
2297                if self.loop_path {
2298                    self.path_index = 0;
2299                } else {
2300                    self.path_index = self.path.len() - 1;
2301                }
2302            }
2303        } else {
2304            self.direction = to_target / dist;
2305            self.position = self.position + self.direction * step;
2306        }
2307    }
2308
2309    pub fn add_waypoint(&mut self, pos: Vec3) {
2310        self.path.push(pos);
2311    }
2312
2313    pub fn reset(&mut self) {
2314        self.path_index = 0;
2315        self.time_accumulated_s = 0.0;
2316        if !self.path.is_empty() {
2317            self.position = self.path[0];
2318        }
2319    }
2320}
2321
2322#[derive(Debug)]
2323pub struct StreamingSimulator {
2324    pub camera: SimulationCamera,
2325    pub simulated_time_ms: f64,
2326    pub simulated_frames: u64,
2327    pub playback_speed: f32,
2328    pub is_running: bool,
2329    pub load_events_simulated: u32,
2330    pub unload_events_simulated: u32,
2331}
2332
2333impl StreamingSimulator {
2334    pub fn new() -> Self {
2335        Self {
2336            camera: SimulationCamera::new(Vec3::ZERO),
2337            simulated_time_ms: 0.0,
2338            simulated_frames: 0,
2339            playback_speed: 1.0,
2340            is_running: false,
2341            load_events_simulated: 0,
2342            unload_events_simulated: 0,
2343        }
2344    }
2345
2346    pub fn tick(&mut self, dt_s: f32) {
2347        if !self.is_running { return; }
2348        let sim_dt = dt_s * self.playback_speed;
2349        self.camera.update(sim_dt);
2350        self.simulated_time_ms += sim_dt as f64 * 1000.0;
2351        self.simulated_frames += 1;
2352    }
2353
2354    pub fn start(&mut self) { self.is_running = true; }
2355    pub fn stop(&mut self) { self.is_running = false; }
2356    pub fn reset(&mut self) {
2357        self.simulated_time_ms = 0.0;
2358        self.simulated_frames = 0;
2359        self.load_events_simulated = 0;
2360        self.unload_events_simulated = 0;
2361        self.camera.reset();
2362    }
2363}
2364
2365// ============================================================
2366// STREAMING BUDGET TOOL
2367// ============================================================
2368
2369#[derive(Debug, Clone)]
2370pub struct BudgetBreakdown {
2371    pub total_budget_mb: f32,
2372    pub used_mb: f32,
2373    pub available_mb: f32,
2374    pub loaded_level_count: usize,
2375    pub loading_level_count: usize,
2376    pub pending_level_count: usize,
2377    pub largest_level_name: String,
2378    pub largest_level_mb: f32,
2379    pub per_category: HashMap<String, f32>,
2380}
2381
2382#[derive(Debug)]
2383pub struct StreamingBudgetTool {
2384    pub breakdown: BudgetBreakdown,
2385    pub history: VecDeque<(f64, f32)>, // (time, used_mb)
2386    pub peak_usage_mb: f32,
2387    pub history_capacity: usize,
2388}
2389
2390impl StreamingBudgetTool {
2391    pub fn new(total_budget_mb: f32) -> Self {
2392        Self {
2393            breakdown: BudgetBreakdown {
2394                total_budget_mb,
2395                used_mb: 0.0,
2396                available_mb: total_budget_mb,
2397                loaded_level_count: 0,
2398                loading_level_count: 0,
2399                pending_level_count: 0,
2400                largest_level_name: String::new(),
2401                largest_level_mb: 0.0,
2402                per_category: HashMap::new(),
2403            },
2404            history: VecDeque::with_capacity(512),
2405            peak_usage_mb: 0.0,
2406            history_capacity: 512,
2407        }
2408    }
2409
2410    pub fn update(&mut self, levels: &[StreamingLevel], time_ms: f64) {
2411        let mut used = 0.0f32;
2412        let mut loaded = 0;
2413        let mut loading = 0;
2414        let mut pending = 0;
2415        let mut largest_mb = 0.0f32;
2416        let mut largest_name = String::new();
2417        let mut per_category: HashMap<String, f32> = HashMap::new();
2418
2419        for level in levels {
2420            match level.state {
2421                StreamingState::Loaded => {
2422                    loaded += 1;
2423                    used += level.memory_footprint_mb;
2424                    let cat = format!("{:?}", level.persistence);
2425                    *per_category.entry(cat).or_insert(0.0) += level.memory_footprint_mb;
2426                    if level.memory_footprint_mb > largest_mb {
2427                        largest_mb = level.memory_footprint_mb;
2428                        largest_name = level.name.clone();
2429                    }
2430                }
2431                StreamingState::Loading => loading += 1,
2432                StreamingState::Queued => pending += 1,
2433                _ => {}
2434            }
2435        }
2436
2437        self.breakdown.used_mb = used;
2438        self.breakdown.available_mb = self.breakdown.total_budget_mb - used;
2439        self.breakdown.loaded_level_count = loaded;
2440        self.breakdown.loading_level_count = loading;
2441        self.breakdown.pending_level_count = pending;
2442        self.breakdown.largest_level_name = largest_name;
2443        self.breakdown.largest_level_mb = largest_mb;
2444        self.breakdown.per_category = per_category;
2445
2446        self.peak_usage_mb = self.peak_usage_mb.max(used);
2447
2448        if self.history.len() >= self.history_capacity {
2449            self.history.pop_front();
2450        }
2451        self.history.push_back((time_ms, used));
2452    }
2453
2454    pub fn usage_percent(&self) -> f32 {
2455        if self.breakdown.total_budget_mb <= 0.0 { return 0.0; }
2456        (self.breakdown.used_mb / self.breakdown.total_budget_mb * 100.0).clamp(0.0, 100.0)
2457    }
2458}
2459
2460// ============================================================
2461// MAP OVERVIEW
2462// ============================================================
2463
2464#[derive(Debug, Clone)]
2465pub struct MapThumbnail {
2466    pub sector_id: u64,
2467    pub screen_rect: [f32; 4], // x, y, w, h in [0,1]
2468    pub color: Vec4,
2469    pub label: String,
2470    pub is_loaded: bool,
2471    pub memory_mb: f32,
2472}
2473
2474#[derive(Debug)]
2475pub struct MapOverview {
2476    pub world_bounds: Aabb,
2477    pub thumbnails: Vec<MapThumbnail>,
2478    pub camera_pos_normalized: Vec2,
2479    pub zoom: f32,
2480    pub selected_level_id: Option<u64>,
2481    pub hovered_level_id: Option<u64>,
2482    pub filter_state: Option<StreamingState>,
2483    pub show_labels: bool,
2484    pub show_memory: bool,
2485}
2486
2487impl MapOverview {
2488    pub fn new(world_bounds: Aabb) -> Self {
2489        Self {
2490            world_bounds,
2491            thumbnails: Vec::new(),
2492            camera_pos_normalized: Vec2::ZERO,
2493            zoom: 1.0,
2494            selected_level_id: None,
2495            hovered_level_id: None,
2496            filter_state: None,
2497            show_labels: true,
2498            show_memory: false,
2499        }
2500    }
2501
2502    pub fn world_to_map_uv(&self, world_pos: Vec3) -> Vec2 {
2503        let size = self.world_bounds.size();
2504        let rel = world_pos - self.world_bounds.min;
2505        Vec2::new(
2506            rel.x / size.x.max(1.0),
2507            rel.z / size.z.max(1.0),
2508        )
2509    }
2510
2511    pub fn map_uv_to_world(&self, uv: Vec2) -> Vec3 {
2512        let size = self.world_bounds.size();
2513        self.world_bounds.min + Vec3::new(
2514            uv.x * size.x,
2515            0.0,
2516            uv.y * size.z,
2517        )
2518    }
2519
2520    pub fn update_thumbnails(&mut self, levels: &[StreamingLevel]) {
2521        self.thumbnails.clear();
2522        let world_size = self.world_bounds.size();
2523        for level in levels {
2524            let min_uv = self.world_to_map_uv(level.bounds.min);
2525            let max_uv = self.world_to_map_uv(level.bounds.max);
2526            let color = match level.state {
2527                StreamingState::Loaded => Vec4::new(0.2, 0.8, 0.2, 0.7),
2528                StreamingState::Loading => Vec4::new(1.0, 1.0, 0.0, 0.7),
2529                StreamingState::Unloaded => Vec4::new(0.3, 0.3, 0.3, 0.5),
2530                _ => Vec4::new(0.5, 0.5, 0.5, 0.5),
2531            };
2532            self.thumbnails.push(MapThumbnail {
2533                sector_id: level.id,
2534                screen_rect: [min_uv.x, min_uv.y, max_uv.x - min_uv.x, max_uv.y - min_uv.y],
2535                color,
2536                label: level.name.clone(),
2537                is_loaded: level.state == StreamingState::Loaded,
2538                memory_mb: level.memory_footprint_mb,
2539            });
2540        }
2541    }
2542
2543    pub fn set_camera_position(&mut self, world_pos: Vec3) {
2544        self.camera_pos_normalized = self.world_to_map_uv(world_pos);
2545    }
2546
2547    pub fn levels_at_map_uv(&self, uv: Vec2) -> Vec<u64> {
2548        self.thumbnails.iter()
2549            .filter(|t| {
2550                let r = t.screen_rect;
2551                uv.x >= r[0] && uv.x <= r[0] + r[2] && uv.y >= r[1] && uv.y <= r[1] + r[3]
2552            })
2553            .map(|t| t.sector_id)
2554            .collect()
2555    }
2556}
2557
2558// ============================================================
2559// MAIN LEVEL STREAMING EDITOR
2560// ============================================================
2561
2562#[derive(Debug)]
2563pub struct LevelStreamingEditorConfig {
2564    pub memory_budget_mb: f32,
2565    pub max_concurrent_loads: usize,
2566    pub default_load_distance: f32,
2567    pub default_unload_distance: f32,
2568    pub default_cell_size: f32,
2569    pub eviction_policy: EvictionPolicy,
2570    pub enable_prefetch: bool,
2571    pub prefetch_lookahead_s: f32,
2572    pub enable_frustum_culling: bool,
2573    pub enable_occlusion_culling: bool,
2574    pub debug_overlay: DebugOverlay,
2575    pub simulation_playback_speed: f32,
2576    pub viewport_width: u32,
2577    pub viewport_height: u32,
2578    pub fov_y_rad: f32,
2579}
2580
2581impl Default for LevelStreamingEditorConfig {
2582    fn default() -> Self {
2583        Self {
2584            memory_budget_mb: 1024.0,
2585            max_concurrent_loads: MAX_CONCURRENT_LOADS,
2586            default_load_distance: 1000.0,
2587            default_unload_distance: 1200.0,
2588            default_cell_size: DEFAULT_CELL_SIZE,
2589            eviction_policy: EvictionPolicy::Lru,
2590            enable_prefetch: true,
2591            prefetch_lookahead_s: PREFETCH_LOOKAHEAD_SECONDS,
2592            enable_frustum_culling: true,
2593            enable_occlusion_culling: true,
2594            debug_overlay: DebugOverlay::None,
2595            simulation_playback_speed: 1.0,
2596            viewport_width: 1920,
2597            viewport_height: 1080,
2598            fov_y_rad: std::f32::consts::FRAC_PI_4,
2599        }
2600    }
2601}
2602
2603#[derive(Debug)]
2604pub struct LevelStreamingEditor {
2605    // Levels
2606    pub levels: HashMap<u64, StreamingLevel>,
2607    pub next_level_id: u64,
2608
2609    // World partition
2610    pub world_grid: WorldPartitionGrid,
2611
2612    // Load queue and memory
2613    pub load_queue: StreamingLoadQueue,
2614    pub memory_manager: MemoryPressureManager,
2615
2616    // Streaming volumes
2617    pub volumes: HashMap<u64, StreamingVolume>,
2618    pub next_volume_id: u64,
2619
2620    // Prefetch
2621    pub prefetcher: PrefetchPredictor,
2622
2623    // Dependencies
2624    pub dependency_graph: DependencyGraph,
2625
2626    // Sectors
2627    pub sector_graph: SectorGraph,
2628
2629    // Persistent state
2630    pub persistent_manager: PersistentLevelManager,
2631
2632    // Debug
2633    pub debug_visualizer: StreamingDebugVisualizer,
2634
2635    // Timeline
2636    pub timeline: StreamingTimeline,
2637
2638    // Budget tool
2639    pub budget_tool: StreamingBudgetTool,
2640
2641    // Map overview
2642    pub map_overview: MapOverview,
2643
2644    // HZB
2645    pub hzb: HierarchicalZBuffer,
2646
2647    // Combined LOD budget
2648    pub lod_budget: CombinedBudgetManager,
2649
2650    // Simulation
2651    pub simulator: StreamingSimulator,
2652
2653    // Camera state
2654    pub camera_position: Vec3,
2655    pub camera_direction: Vec3,
2656    pub camera_view_proj: Mat4,
2657
2658    // Config
2659    pub config: LevelStreamingEditorConfig,
2660
2661    // Frame tracking
2662    pub current_frame: u64,
2663    pub current_time_ms: f64,
2664
2665    // Statistics
2666    pub stats: StreamingStats,
2667}
2668
2669#[derive(Debug, Default, Clone)]
2670pub struct StreamingStats {
2671    pub total_levels: usize,
2672    pub loaded_levels: usize,
2673    pub loading_levels: usize,
2674    pub queued_levels: usize,
2675    pub unloaded_levels: usize,
2676    pub frustum_culled_levels: usize,
2677    pub occlusion_culled_levels: usize,
2678    pub total_memory_mb: f32,
2679    pub bandwidth_mb_s: f32,
2680    pub frame_load_count: u32,
2681    pub frame_unload_count: u32,
2682    pub prefetch_hits: u32,
2683    pub prefetch_misses: u32,
2684}
2685
2686impl LevelStreamingEditor {
2687    pub fn new(config: LevelStreamingEditorConfig) -> Self {
2688        let budget_mb = config.memory_budget_mb;
2689        let viewport_w = config.viewport_width as usize;
2690        let viewport_h = config.viewport_height as usize;
2691
2692        Self {
2693            levels: HashMap::new(),
2694            next_level_id: 1,
2695            world_grid: WorldPartitionGrid::new(config.default_cell_size, Vec3::ZERO),
2696            load_queue: StreamingLoadQueue::new(config.max_concurrent_loads),
2697            memory_manager: MemoryPressureManager::new(budget_mb),
2698            volumes: HashMap::new(),
2699            next_volume_id: 1,
2700            prefetcher: PrefetchPredictor::new(config.prefetch_lookahead_s),
2701            dependency_graph: DependencyGraph::new(),
2702            sector_graph: SectorGraph::new(),
2703            persistent_manager: PersistentLevelManager::new(0),
2704            debug_visualizer: StreamingDebugVisualizer::new(),
2705            timeline: StreamingTimeline::new(LEVEL_TIMELINE_CAPACITY),
2706            budget_tool: StreamingBudgetTool::new(budget_mb),
2707            map_overview: MapOverview::new(Aabb::new(-Vec3::splat(5000.0), Vec3::splat(5000.0))),
2708            hzb: HierarchicalZBuffer::new(viewport_w / 4, viewport_h / 4),
2709            lod_budget: CombinedBudgetManager::new(budget_mb),
2710            simulator: StreamingSimulator::new(),
2711            camera_position: Vec3::ZERO,
2712            camera_direction: Vec3::NEG_Z,
2713            camera_view_proj: Mat4::IDENTITY,
2714            config,
2715            current_frame: 0,
2716            current_time_ms: 0.0,
2717            stats: StreamingStats::default(),
2718        }
2719    }
2720
2721    pub fn add_level(&mut self, name: String, asset: StreamingLevelAsset, bounds: Aabb) -> u64 {
2722        let id = self.next_level_id;
2723        self.next_level_id += 1;
2724        let bounds_center = bounds.center();
2725        let mut level = StreamingLevel::new(id, name, asset, bounds);
2726        level.load_distance = self.config.default_load_distance;
2727        level.unload_distance = self.config.default_unload_distance;
2728        self.dependency_graph.add_level(id);
2729
2730        // Register in world grid
2731        let cell = self.world_grid.world_to_cell(bounds_center);
2732        {
2733            let c = self.world_grid.get_or_create_cell(cell);
2734            c.level_ids.push(id);
2735        }
2736
2737        self.levels.insert(id, level);
2738        id
2739    }
2740
2741    pub fn remove_level(&mut self, id: u64) {
2742        if let Some(level) = self.levels.remove(&id) {
2743            // Remove from grid
2744            let cell = self.world_grid.world_to_cell(level.bounds.center());
2745            if let Some(c) = self.world_grid.cells.get_mut(&cell) {
2746                c.level_ids.retain(|&lid| lid != id);
2747            }
2748            // Remove from dependency graph
2749            self.dependency_graph.nodes.remove(&id);
2750        }
2751    }
2752
2753    pub fn add_streaming_volume_sphere(&mut self, name: String, center: Vec3, load_r: f32, unload_r: f32) -> u64 {
2754        let id = self.next_volume_id;
2755        self.next_volume_id += 1;
2756        let vol = StreamingVolume::new_sphere(id, name, center, load_r, unload_r);
2757        self.volumes.insert(id, vol);
2758        id
2759    }
2760
2761    pub fn add_streaming_volume_box(&mut self, name: String, transform: Mat4, half_extents: Vec3) -> u64 {
2762        let id = self.next_volume_id;
2763        self.next_volume_id += 1;
2764        let vol = StreamingVolume::new_box(id, name, transform, half_extents);
2765        self.volumes.insert(id, vol);
2766        id
2767    }
2768
2769    pub fn update_camera(&mut self, position: Vec3, direction: Vec3, view_proj: Mat4) {
2770        self.camera_position = position;
2771        self.camera_direction = direction;
2772        self.camera_view_proj = view_proj;
2773        self.prefetcher.update(position, self.current_time_ms);
2774        self.map_overview.set_camera_position(position);
2775    }
2776
2777    /// Main per-frame update
2778    pub fn tick(&mut self, dt_s: f32) {
2779        self.current_frame += 1;
2780        self.current_time_ms += dt_s as f64 * 1000.0;
2781
2782        // Update simulator if running
2783        self.simulator.tick(dt_s);
2784        let cam_pos = if self.simulator.is_running {
2785            self.simulator.camera.position
2786        } else {
2787            self.camera_position
2788        };
2789
2790        self.stats.frame_load_count = 0;
2791        self.stats.frame_unload_count = 0;
2792
2793        self.update_distance_and_culling(cam_pos);
2794        self.process_streaming_volumes(cam_pos);
2795        self.update_load_unload_decisions(cam_pos);
2796        self.process_prefetch(cam_pos);
2797        self.process_load_queue();
2798        self.manage_memory_pressure(cam_pos);
2799        self.update_lod_budget(cam_pos);
2800        self.update_debug_visualization();
2801        self.budget_tool.update(&self.levels.values().cloned().collect::<Vec<_>>(), self.current_time_ms);
2802        self.map_overview.update_thumbnails(&self.levels.values().cloned().collect::<Vec<_>>());
2803
2804        self.collect_stats();
2805
2806        self.timeline.record_memory(self.current_time_ms, self.memory_manager.used_mb);
2807        self.timeline.record_bandwidth(
2808            self.current_time_ms,
2809            self.load_queue.estimated_bandwidth_mb_s(),
2810        );
2811    }
2812
2813    fn update_distance_and_culling(&mut self, cam_pos: Vec3) {
2814        let frustum = Frustum::from_view_proj(self.camera_view_proj);
2815        let vp_h = self.config.viewport_height as f32;
2816        let fov = self.config.fov_y_rad;
2817        let mut frustum_culled = 0;
2818        let mut occlusion_culled = 0;
2819
2820        let level_ids: Vec<u64> = self.levels.keys().copied().collect();
2821        for id in level_ids {
2822            if let Some(level) = self.levels.get_mut(&id) {
2823                let dist = level.bounds.distance_to_point(cam_pos);
2824                level.distance_to_camera = dist;
2825                level.screen_size = level.compute_screen_size(cam_pos, fov, vp_h);
2826                level.current_lod = level.compute_lod(dist, level.lod_bias);
2827
2828                // Frustum cull
2829                if self.config.enable_frustum_culling {
2830                    level.is_frustum_culled = !frustum.test_aabb(&level.bounds);
2831                    if level.is_frustum_culled { frustum_culled += 1; }
2832                } else {
2833                    level.is_frustum_culled = false;
2834                }
2835
2836                // Occlusion cull (only if loaded and visible)
2837                if self.config.enable_occlusion_culling && !level.is_frustum_culled {
2838                    level.is_occlusion_culled = !self.hzb.test_aabb_visibility(&level.bounds, &self.camera_view_proj);
2839                    if level.is_occlusion_culled { occlusion_culled += 1; }
2840                } else {
2841                    level.is_occlusion_culled = false;
2842                }
2843
2844                level.is_visible = !level.is_frustum_culled && !level.is_occlusion_culled;
2845            }
2846        }
2847
2848        self.stats.frustum_culled_levels = frustum_culled;
2849        self.stats.occlusion_culled_levels = occlusion_culled;
2850    }
2851
2852    fn process_streaming_volumes(&mut self, cam_pos: Vec3) {
2853        let volume_ids: Vec<u64> = self.volumes.keys().copied().collect();
2854        for vid in volume_ids {
2855            if let Some(vol) = self.volumes.get(&vid) {
2856                if !vol.is_enabled { continue; }
2857                let should_load = vol.should_trigger_load(cam_pos);
2858                let target_ids = vol.target_level_ids.clone();
2859                let importance = vol.importance_weight;
2860                for lid in target_ids {
2861                    if let Some(level) = self.levels.get_mut(&lid) {
2862                        if should_load && level.state == StreamingState::Unloaded {
2863                            level.importance_weight = level.importance_weight.max(importance);
2864                        }
2865                    }
2866                }
2867            }
2868        }
2869    }
2870
2871    fn update_load_unload_decisions(&mut self, cam_pos: Vec3) {
2872        let level_ids: Vec<u64> = self.levels.keys().copied().collect();
2873        for id in level_ids {
2874            let (should_load, should_unload, dist, importance, name) = {
2875                if let Some(level) = self.levels.get(&id) {
2876                    let sl = level.should_load(cam_pos);
2877                    let su = level.should_unload(cam_pos);
2878                    (sl, su, level.distance_to_camera, level.importance_weight, level.name.clone())
2879                } else { continue }
2880            };
2881
2882            if let Some(level) = self.levels.get_mut(&id) {
2883                match level.state {
2884                    StreamingState::Unloaded | StreamingState::Evicted => {
2885                        if should_load {
2886                            let mem_est = level.memory_estimate_mb();
2887                            if self.memory_manager.can_load(mem_est) || level.persistence == LevelPersistence::AlwaysLoaded {
2888                                level.state = StreamingState::Queued;
2889                                let dist_weight = 1.0 / (dist + 1.0) * importance;
2890                                self.load_queue.enqueue(LoadRequest {
2891                                    level_id: id,
2892                                    priority: level.priority,
2893                                    distance_weight: dist_weight,
2894                                    enqueue_time_ms: self.current_time_ms,
2895                                    predicted_load_time_ms: level.asset.load_time_estimate_ms,
2896                                    is_prefetch: false,
2897                                });
2898                            }
2899                        }
2900                    }
2901                    StreamingState::Loaded => {
2902                        if should_unload {
2903                            level.state = StreamingState::Unloading;
2904                            self.stats.frame_unload_count += 1;
2905                        }
2906                    }
2907                    StreamingState::Unloading => {
2908                        // Simulate unload completion (in real engine: async)
2909                        let size = level.memory_footprint_mb;
2910                        self.memory_manager.record_unload(id, size);
2911                        level.state = StreamingState::Unloaded;
2912                        level.unload_timestamp_ms = self.current_time_ms;
2913                        let event = StreamingEvent {
2914                            timestamp_ms: self.current_time_ms,
2915                            kind: StreamingEventKind::LevelUnloadCompleted,
2916                            level_id: id,
2917                            level_name: name.clone(),
2918                            data_mb: size,
2919                            duration_ms: 16.0, // simulated
2920                            camera_pos: cam_pos,
2921                        };
2922                        self.timeline.record(event);
2923                    }
2924                    _ => {}
2925                }
2926            }
2927        }
2928    }
2929
2930    fn process_prefetch(&mut self, cam_pos: Vec3) {
2931        if !self.config.enable_prefetch { return; }
2932        let levels_vec: Vec<StreamingLevel> = self.levels.values().cloned().collect();
2933        let candidates = self.prefetcher.get_prefetch_candidates(&levels_vec, cam_pos);
2934        for lid in candidates {
2935            if let Some(level) = self.levels.get_mut(&lid) {
2936                if level.state == StreamingState::Unloaded {
2937                    level.state = StreamingState::Queued;
2938                    self.load_queue.enqueue(LoadRequest {
2939                        level_id: lid,
2940                        priority: LoadPriority::Prefetch,
2941                        distance_weight: 0.1,
2942                        enqueue_time_ms: self.current_time_ms,
2943                        predicted_load_time_ms: level.asset.load_time_estimate_ms,
2944                        is_prefetch: true,
2945                    });
2946                }
2947            }
2948        }
2949    }
2950
2951    fn process_load_queue(&mut self) {
2952        while let Some(request) = self.load_queue.dequeue_next() {
2953            if let Some(level) = self.levels.get_mut(&request.level_id) {
2954                if level.state == StreamingState::Queued || level.state == StreamingState::Unloaded {
2955                    level.state = StreamingState::Loading;
2956                    self.stats.frame_load_count += 1;
2957                }
2958            }
2959        }
2960
2961        // Simulate load completion for all in-flight levels
2962        let in_flight_ids: Vec<u64> = self.load_queue.in_flight.iter().map(|r| r.level_id).collect();
2963        for lid in in_flight_ids {
2964            // Simulated instant completion (real engine: check async result)
2965            let (size_bytes, name, time_ms) = {
2966                if let Some(level) = self.levels.get(&lid) {
2967                    (level.asset.size_bytes, level.name.clone(), level.asset.load_time_estimate_ms)
2968                } else { continue }
2969            };
2970
2971            self.load_queue.complete_load(lid, size_bytes, time_ms);
2972            let size_mb = size_bytes as f32 / (1024.0 * 1024.0);
2973            self.memory_manager.record_load(lid, size_mb);
2974
2975            if let Some(level) = self.levels.get_mut(&lid) {
2976                level.state = StreamingState::Loaded;
2977                level.memory_footprint_mb = size_mb;
2978                level.load_timestamp_ms = self.current_time_ms;
2979                level.load_count += 1;
2980            }
2981
2982            let event = StreamingEvent {
2983                timestamp_ms: self.current_time_ms,
2984                kind: StreamingEventKind::LevelLoadCompleted,
2985                level_id: lid,
2986                level_name: name,
2987                data_mb: size_mb,
2988                duration_ms: time_ms,
2989                camera_pos: self.camera_position,
2990            };
2991            self.timeline.record(event);
2992        }
2993    }
2994
2995    fn manage_memory_pressure(&mut self, cam_pos: Vec3) {
2996        if !self.memory_manager.is_under_pressure() { return; }
2997
2998        let needed = self.memory_manager.used_mb - self.memory_manager.budget_mb * self.memory_manager.pressure_threshold;
2999        let levels_vec: Vec<StreamingLevel> = self.levels.values().cloned().collect();
3000        let candidates = self.memory_manager.select_eviction_candidates(needed, &levels_vec);
3001
3002        for lid in candidates {
3003            if let Some(level) = self.levels.get_mut(&lid) {
3004                if level.state == StreamingState::Loaded {
3005                    let size = level.memory_footprint_mb;
3006                    self.memory_manager.record_unload(lid, size);
3007                    level.state = StreamingState::Evicted;
3008                }
3009            }
3010        }
3011    }
3012
3013    fn update_lod_budget(&mut self, cam_pos: Vec3) {
3014        let level_ids: Vec<u64> = self.levels.keys().copied().collect();
3015        for id in level_ids {
3016            if let Some(level) = self.levels.get(&id) {
3017                let lod_mem = level.memory_estimate_mb();
3018                let stream_mem = if level.state == StreamingState::Loaded { 5.0 } else { 0.0 };
3019                self.lod_budget.update_entry(id, level.current_lod, lod_mem, stream_mem);
3020            }
3021        }
3022    }
3023
3024    fn update_debug_visualization(&mut self) {
3025        self.debug_visualizer.clear();
3026        match self.config.debug_overlay {
3027            DebugOverlay::None => {}
3028            DebugOverlay::StreamingState => {
3029                for level in self.levels.values() {
3030                    self.debug_visualizer.draw_level_bounds(level);
3031                }
3032            }
3033            DebugOverlay::MemoryUsage => {
3034                for level in self.levels.values() {
3035                    self.debug_visualizer.draw_level_bounds(level);
3036                    self.debug_visualizer.draw_memory_label(level);
3037                }
3038            }
3039            DebugOverlay::LoadDistance => {
3040                for level in self.levels.values() {
3041                    self.debug_visualizer.draw_load_distance_sphere(level);
3042                }
3043            }
3044            DebugOverlay::CellGrid => {
3045                self.debug_visualizer.update_heatmap(&self.world_grid);
3046                self.debug_visualizer.draw_cell_grid(&self.world_grid, self.camera_position);
3047            }
3048            DebugOverlay::PriorityHeatmap => {
3049                self.debug_visualizer.update_heatmap(&self.world_grid);
3050                for level in self.levels.values() {
3051                    self.debug_visualizer.draw_level_bounds(level);
3052                }
3053            }
3054            _ => {}
3055        }
3056    }
3057
3058    fn collect_stats(&mut self) {
3059        let mut total = 0;
3060        let mut loaded = 0;
3061        let mut loading = 0;
3062        let mut queued = 0;
3063        let mut unloaded = 0;
3064        let mut memory = 0.0f32;
3065
3066        for level in self.levels.values() {
3067            total += 1;
3068            memory += level.memory_footprint_mb;
3069            match level.state {
3070                StreamingState::Loaded => loaded += 1,
3071                StreamingState::Loading => loading += 1,
3072                StreamingState::Queued => queued += 1,
3073                StreamingState::Unloaded | StreamingState::Evicted => unloaded += 1,
3074                _ => {}
3075            }
3076        }
3077
3078        self.stats.total_levels = total;
3079        self.stats.loaded_levels = loaded;
3080        self.stats.loading_levels = loading;
3081        self.stats.queued_levels = queued;
3082        self.stats.unloaded_levels = unloaded;
3083        self.stats.total_memory_mb = memory;
3084        self.stats.bandwidth_mb_s = self.load_queue.estimated_bandwidth_mb_s();
3085    }
3086
3087    pub fn set_debug_overlay(&mut self, overlay: DebugOverlay) {
3088        self.config.debug_overlay = overlay;
3089        self.debug_visualizer.overlay = overlay;
3090    }
3091
3092    pub fn get_level(&self, id: u64) -> Option<&StreamingLevel> {
3093        self.levels.get(&id)
3094    }
3095
3096    pub fn get_level_mut(&mut self, id: u64) -> Option<&mut StreamingLevel> {
3097        self.levels.get_mut(&id)
3098    }
3099
3100    pub fn force_load_level(&mut self, id: u64) {
3101        if let Some(level) = self.levels.get_mut(&id) {
3102            level.state = StreamingState::Queued;
3103            level.priority = LoadPriority::Critical;
3104            let lid = level.id;
3105            let time_ms = level.asset.load_time_estimate_ms;
3106            self.load_queue.enqueue(LoadRequest {
3107                level_id: lid,
3108                priority: LoadPriority::Critical,
3109                distance_weight: 1000.0,
3110                enqueue_time_ms: self.current_time_ms,
3111                predicted_load_time_ms: time_ms,
3112                is_prefetch: false,
3113            });
3114        }
3115    }
3116
3117    pub fn force_unload_level(&mut self, id: u64) {
3118        if let Some(level) = self.levels.get_mut(&id) {
3119            if level.persistence != LevelPersistence::AlwaysLoaded {
3120                level.state = StreamingState::Unloading;
3121            }
3122        }
3123    }
3124
3125    pub fn set_memory_budget(&mut self, budget_mb: f32) {
3126        self.config.memory_budget_mb = budget_mb;
3127        self.memory_manager.budget_mb = budget_mb;
3128        self.budget_tool.breakdown.total_budget_mb = budget_mb;
3129        self.lod_budget.total_budget_mb = budget_mb;
3130    }
3131
3132    pub fn add_level_dependency(&mut self, from: u64, to: u64, edge: DependencyEdgeType) {
3133        self.dependency_graph.add_dependency(from, to, edge);
3134    }
3135
3136    pub fn check_for_circular_dependencies(&self) -> Option<Vec<u64>> {
3137        self.dependency_graph.detect_cycles()
3138    }
3139
3140    pub fn get_load_plan(&self) -> Vec<Vec<u64>> {
3141        self.dependency_graph.parallel_load_plan()
3142    }
3143
3144    pub fn start_simulation(&mut self) {
3145        self.simulator.start();
3146    }
3147
3148    pub fn stop_simulation(&mut self) {
3149        self.simulator.stop();
3150    }
3151
3152    pub fn reset_simulation(&mut self) {
3153        self.simulator.reset();
3154        // Reset all levels to unloaded
3155        for level in self.levels.values_mut() {
3156            if level.persistence != LevelPersistence::AlwaysLoaded {
3157                level.state = StreamingState::Unloaded;
3158                level.memory_footprint_mb = 0.0;
3159            }
3160        }
3161        self.memory_manager.used_mb = 0.0;
3162        self.memory_manager.lru_order.clear();
3163        self.load_queue.pending.clear();
3164        self.load_queue.in_flight.clear();
3165    }
3166
3167    pub fn get_world_bounds(&self) -> Aabb {
3168        let mut result = Aabb::new(Vec3::splat(f32::MAX), Vec3::splat(f32::MIN));
3169        for level in self.levels.values() {
3170            result = result.merge(&level.bounds);
3171        }
3172        if result.min.x > result.max.x {
3173            Aabb::new(Vec3::ZERO, Vec3::ZERO)
3174        } else {
3175            result
3176        }
3177    }
3178
3179    pub fn cells_in_camera_radius(&self, radius: f32) -> Vec<CellCoord> {
3180        self.world_grid.get_cells_in_radius(self.camera_position, radius)
3181    }
3182
3183    pub fn query_levels_near(&self, pos: Vec3, radius: f32) -> Vec<u64> {
3184        self.levels.values()
3185            .filter(|l| l.bounds.distance_to_point(pos) <= radius)
3186            .map(|l| l.id)
3187            .collect()
3188    }
3189
3190    pub fn get_streaming_report(&self) -> StreamingReport {
3191        StreamingReport {
3192            total_levels: self.stats.total_levels,
3193            loaded_count: self.stats.loaded_levels,
3194            loading_count: self.stats.loading_levels,
3195            queued_count: self.stats.queued_levels,
3196            total_memory_mb: self.stats.total_memory_mb,
3197            memory_budget_mb: self.config.memory_budget_mb,
3198            bandwidth_mb_s: self.stats.bandwidth_mb_s,
3199            estimated_queue_time_ms: self.load_queue.estimated_remaining_time_ms(),
3200            has_circular_deps: self.dependency_graph.detect_cycles().is_some(),
3201            memory_pressure_ratio: self.memory_manager.pressure_ratio(),
3202            current_sector: self.sector_graph.current_sector,
3203            global_lod_bias: self.lod_budget.compute_global_lod_bias(),
3204        }
3205    }
3206}
3207
3208#[derive(Debug, Clone)]
3209pub struct StreamingReport {
3210    pub total_levels: usize,
3211    pub loaded_count: usize,
3212    pub loading_count: usize,
3213    pub queued_count: usize,
3214    pub total_memory_mb: f32,
3215    pub memory_budget_mb: f32,
3216    pub bandwidth_mb_s: f32,
3217    pub estimated_queue_time_ms: f32,
3218    pub has_circular_deps: bool,
3219    pub memory_pressure_ratio: f32,
3220    pub current_sector: Option<u64>,
3221    pub global_lod_bias: f32,
3222}
3223
3224// ============================================================
3225// ADVANCED STREAMING FEATURES
3226// ============================================================
3227
3228/// Bandwidth estimator using exponential moving average
3229#[derive(Debug)]
3230pub struct BandwidthEstimator {
3231    pub ema: f32,
3232    pub alpha: f32, // EMA smoothing factor
3233    pub peak_mb_s: f32,
3234    pub min_mb_s: f32,
3235    pub sample_count: u64,
3236}
3237
3238impl BandwidthEstimator {
3239    pub fn new(initial_estimate_mb_s: f32) -> Self {
3240        Self {
3241            ema: initial_estimate_mb_s,
3242            alpha: 0.1,
3243            peak_mb_s: initial_estimate_mb_s,
3244            min_mb_s: initial_estimate_mb_s,
3245            sample_count: 0,
3246        }
3247    }
3248
3249    pub fn add_sample(&mut self, mb_per_s: f32) {
3250        self.ema = self.alpha * mb_per_s + (1.0 - self.alpha) * self.ema;
3251        self.peak_mb_s = self.peak_mb_s.max(mb_per_s);
3252        self.min_mb_s = if self.sample_count == 0 { mb_per_s } else { self.min_mb_s.min(mb_per_s) };
3253        self.sample_count += 1;
3254    }
3255
3256    pub fn estimate(&self) -> f32 { self.ema }
3257
3258    pub fn estimated_load_time_ms(&self, size_mb: f32) -> f32 {
3259        if self.ema <= 0.0 { return f32::MAX; }
3260        size_mb / self.ema * 1000.0
3261    }
3262
3263    pub fn variance_adjusted_estimate(&self, size_mb: f32, confidence: f32) -> f32 {
3264        // Use pessimistic estimate for high confidence requirements
3265        let rate = if confidence > 0.9 {
3266            self.min_mb_s.max(self.ema * 0.5)
3267        } else {
3268            self.ema
3269        };
3270        if rate <= 0.0 { return f32::MAX; }
3271        size_mb / rate * 1000.0
3272    }
3273}
3274
3275/// Occlusion query result cache
3276#[derive(Debug)]
3277pub struct OcclusionCache {
3278    pub results: HashMap<u64, (bool, u64)>, // level_id -> (is_visible, frame)
3279    pub cache_lifetime_frames: u64,
3280}
3281
3282impl OcclusionCache {
3283    pub fn new(lifetime_frames: u64) -> Self {
3284        Self {
3285            results: HashMap::new(),
3286            cache_lifetime_frames: lifetime_frames,
3287        }
3288    }
3289
3290    pub fn get(&self, level_id: u64, current_frame: u64) -> Option<bool> {
3291        if let Some(&(visible, frame)) = self.results.get(&level_id) {
3292            if current_frame - frame <= self.cache_lifetime_frames {
3293                return Some(visible);
3294            }
3295        }
3296        None
3297    }
3298
3299    pub fn set(&mut self, level_id: u64, visible: bool, frame: u64) {
3300        self.results.insert(level_id, (visible, frame));
3301    }
3302
3303    pub fn evict_stale(&mut self, current_frame: u64) {
3304        self.results.retain(|_, (_, frame)| {
3305            current_frame - *frame <= self.cache_lifetime_frames
3306        });
3307    }
3308}
3309
3310/// World streaming bandwidth tracker
3311#[derive(Debug)]
3312pub struct WorldStreamingBandwidthTracker {
3313    pub estimator: BandwidthEstimator,
3314    pub frame_data: VecDeque<(u64, f32)>, // (frame, MB this frame)
3315    pub total_mb_streamed: f32,
3316    pub peak_frame_mb: f32,
3317}
3318
3319impl WorldStreamingBandwidthTracker {
3320    pub fn new() -> Self {
3321        Self {
3322            estimator: BandwidthEstimator::new(50.0),
3323            frame_data: VecDeque::with_capacity(256),
3324            total_mb_streamed: 0.0,
3325            peak_frame_mb: 0.0,
3326        }
3327    }
3328
3329    pub fn record_frame(&mut self, frame: u64, mb_this_frame: f32, dt_s: f32) {
3330        if self.frame_data.len() >= 256 { self.frame_data.pop_front(); }
3331        self.frame_data.push_back((frame, mb_this_frame));
3332        self.total_mb_streamed += mb_this_frame;
3333        self.peak_frame_mb = self.peak_frame_mb.max(mb_this_frame);
3334        if dt_s > 1e-6 {
3335            self.estimator.add_sample(mb_this_frame / dt_s);
3336        }
3337    }
3338
3339    pub fn average_mb_per_frame(&self) -> f32 {
3340        if self.frame_data.is_empty() { return 0.0; }
3341        let total: f32 = self.frame_data.iter().map(|(_, mb)| mb).sum();
3342        total / self.frame_data.len() as f32
3343    }
3344}
3345
3346/// Level streaming profiler
3347#[derive(Debug)]
3348pub struct LevelStreamingProfiler {
3349    pub frame_timings: VecDeque<f32>,        // ms per frame for streaming logic
3350    pub load_timings: HashMap<u64, f32>,     // level_id -> last load time ms
3351    pub queue_depth_history: VecDeque<usize>,
3352    pub memory_history_full: VecDeque<f32>,
3353    pub bandwidth_tracker: WorldStreamingBandwidthTracker,
3354    pub total_frames_profiled: u64,
3355}
3356
3357impl LevelStreamingProfiler {
3358    pub fn new() -> Self {
3359        Self {
3360            frame_timings: VecDeque::with_capacity(256),
3361            load_timings: HashMap::new(),
3362            queue_depth_history: VecDeque::with_capacity(256),
3363            memory_history_full: VecDeque::with_capacity(256),
3364            bandwidth_tracker: WorldStreamingBandwidthTracker::new(),
3365            total_frames_profiled: 0,
3366        }
3367    }
3368
3369    pub fn record_frame(&mut self, timing_ms: f32, queue_depth: usize, memory_mb: f32, streamed_mb: f32, dt_s: f32) {
3370        if self.frame_timings.len() >= 256 { self.frame_timings.pop_front(); }
3371        if self.queue_depth_history.len() >= 256 { self.queue_depth_history.pop_front(); }
3372        if self.memory_history_full.len() >= 256 { self.memory_history_full.pop_front(); }
3373
3374        self.frame_timings.push_back(timing_ms);
3375        self.queue_depth_history.push_back(queue_depth);
3376        self.memory_history_full.push_back(memory_mb);
3377        self.bandwidth_tracker.record_frame(self.total_frames_profiled, streamed_mb, dt_s);
3378        self.total_frames_profiled += 1;
3379    }
3380
3381    pub fn average_frame_time_ms(&self) -> f32 {
3382        if self.frame_timings.is_empty() { return 0.0; }
3383        self.frame_timings.iter().sum::<f32>() / self.frame_timings.len() as f32
3384    }
3385
3386    pub fn p99_frame_time_ms(&self) -> f32 {
3387        let mut sorted: Vec<f32> = self.frame_timings.iter().copied().collect();
3388        sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3389        let idx = (sorted.len() as f32 * 0.99) as usize;
3390        sorted.get(idx).copied().unwrap_or(0.0)
3391    }
3392
3393    pub fn max_queue_depth(&self) -> usize {
3394        self.queue_depth_history.iter().copied().max().unwrap_or(0)
3395    }
3396
3397    pub fn peak_memory_mb(&self) -> f32 {
3398        self.memory_history_full.iter().copied().fold(0.0f32, f32::max)
3399    }
3400}
3401
3402// ============================================================
3403// LEVEL INSTANCE MANAGEMENT
3404// ============================================================
3405
3406#[derive(Debug, Clone)]
3407pub struct LevelInstance {
3408    pub instance_id: u64,
3409    pub level_id: u64,
3410    pub transform: Mat4,
3411    pub override_lod: Option<LodLevel>,
3412    pub visible: bool,
3413    pub cast_shadows: bool,
3414    pub custom_culling_distance: Option<f32>,
3415    pub tags: HashSet<String>,
3416    pub metadata: HashMap<String, String>,
3417    pub creation_time: f64,
3418    pub last_modified_time: f64,
3419    pub override_load_distance: Option<f32>,
3420    pub override_priority: Option<LoadPriority>,
3421}
3422
3423impl LevelInstance {
3424    pub fn new(instance_id: u64, level_id: u64, transform: Mat4) -> Self {
3425        Self {
3426            instance_id,
3427            level_id,
3428            transform,
3429            override_lod: None,
3430            visible: true,
3431            cast_shadows: true,
3432            custom_culling_distance: None,
3433            tags: HashSet::new(),
3434            metadata: HashMap::new(),
3435            creation_time: 0.0,
3436            last_modified_time: 0.0,
3437            override_load_distance: None,
3438            override_priority: None,
3439        }
3440    }
3441
3442    pub fn world_position(&self) -> Vec3 {
3443        self.transform.transform_point3(Vec3::ZERO)
3444    }
3445
3446    pub fn position(&self) -> Vec3 {
3447        Vec3::new(self.transform.w_axis.x, self.transform.w_axis.y, self.transform.w_axis.z)
3448    }
3449
3450    pub fn rotation_quat(&self) -> Quat {
3451        let m = &self.transform;
3452        let sx = Vec3::new(m.x_axis.x, m.x_axis.y, m.x_axis.z).length();
3453        let sy = Vec3::new(m.y_axis.x, m.y_axis.y, m.y_axis.z).length();
3454        let sz = Vec3::new(m.z_axis.x, m.z_axis.y, m.z_axis.z).length();
3455        let rm = Mat4::from_cols(
3456            m.x_axis / sx,
3457            m.y_axis / sy,
3458            m.z_axis / sz,
3459            Vec4::W,
3460        );
3461        Quat::from_mat4(&rm)
3462    }
3463
3464    pub fn scale(&self) -> Vec3 {
3465        let m = &self.transform;
3466        Vec3::new(
3467            Vec3::new(m.x_axis.x, m.x_axis.y, m.x_axis.z).length(),
3468            Vec3::new(m.y_axis.x, m.y_axis.y, m.y_axis.z).length(),
3469            Vec3::new(m.z_axis.x, m.z_axis.y, m.z_axis.z).length(),
3470        )
3471    }
3472
3473    pub fn add_tag(&mut self, tag: &str) {
3474        self.tags.insert(tag.to_string());
3475    }
3476
3477    pub fn has_tag(&self, tag: &str) -> bool {
3478        self.tags.contains(tag)
3479    }
3480
3481    pub fn set_metadata(&mut self, key: &str, value: &str) {
3482        self.metadata.insert(key.to_string(), value.to_string());
3483        self.last_modified_time += 0.001;
3484    }
3485}
3486
3487/// Manages multiple instances of the same level template
3488#[derive(Debug)]
3489pub struct LevelInstanceManager {
3490    pub instances: HashMap<u64, LevelInstance>,
3491    pub next_instance_id: u64,
3492    pub instance_to_base: HashMap<u64, u64>, // instance_id -> level_id
3493    pub base_to_instances: HashMap<u64, Vec<u64>>, // level_id -> [instance_ids]
3494}
3495
3496impl LevelInstanceManager {
3497    pub fn new() -> Self {
3498        Self {
3499            instances: HashMap::new(),
3500            next_instance_id: 1,
3501            instance_to_base: HashMap::new(),
3502            base_to_instances: HashMap::new(),
3503        }
3504    }
3505
3506    pub fn instantiate(&mut self, level_id: u64, transform: Mat4) -> u64 {
3507        let id = self.next_instance_id;
3508        self.next_instance_id += 1;
3509        self.instances.insert(id, LevelInstance::new(id, level_id, transform));
3510        self.instance_to_base.insert(id, level_id);
3511        self.base_to_instances.entry(level_id).or_default().push(id);
3512        id
3513    }
3514
3515    pub fn remove_instance(&mut self, instance_id: u64) {
3516        if let Some(inst) = self.instances.remove(&instance_id) {
3517            self.instance_to_base.remove(&instance_id);
3518            if let Some(list) = self.base_to_instances.get_mut(&inst.level_id) {
3519                list.retain(|&id| id != instance_id);
3520            }
3521        }
3522    }
3523
3524    pub fn get_instances_for_level(&self, level_id: u64) -> Vec<&LevelInstance> {
3525        self.base_to_instances.get(&level_id)
3526            .map(|ids| ids.iter().filter_map(|id| self.instances.get(id)).collect())
3527            .unwrap_or_default()
3528    }
3529
3530    pub fn instances_near(&self, pos: Vec3, radius: f32) -> Vec<u64> {
3531        self.instances.values()
3532            .filter(|inst| {
3533                let wp = inst.world_position();
3534                (wp - pos).length() <= radius
3535            })
3536            .map(|inst| inst.instance_id)
3537            .collect()
3538    }
3539
3540    pub fn active_instance_count(&self) -> usize {
3541        self.instances.values().filter(|i| i.visible).count()
3542    }
3543}
3544
3545// ============================================================
3546// STREAMING EDITOR UI STATE
3547// ============================================================
3548
3549#[derive(Debug, Clone, PartialEq, Eq)]
3550pub enum EditorPanel {
3551    MapOverview,
3552    LevelList,
3553    BudgetTool,
3554    Timeline,
3555    Settings,
3556    DependencyGraph,
3557    SectorEditor,
3558    SimulationControl,
3559    Profiler,
3560}
3561
3562#[derive(Debug)]
3563pub struct LevelStreamingEditorUiState {
3564    pub active_panel: EditorPanel,
3565    pub selected_level_ids: HashSet<u64>,
3566    pub search_filter: String,
3567    pub state_filter: Option<StreamingState>,
3568    pub sort_by_distance: bool,
3569    pub sort_by_memory: bool,
3570    pub show_only_visible: bool,
3571    pub timeline_scroll_x: f32,
3572    pub timeline_zoom: f32,
3573    pub map_scroll: Vec2,
3574    pub map_zoom: f32,
3575    pub is_editing_volume: bool,
3576    pub editing_volume_id: Option<u64>,
3577    pub simulation_panel_open: bool,
3578    pub budget_panel_open: bool,
3579    pub dep_graph_panel_open: bool,
3580}
3581
3582impl LevelStreamingEditorUiState {
3583    pub fn new() -> Self {
3584        Self {
3585            active_panel: EditorPanel::MapOverview,
3586            selected_level_ids: HashSet::new(),
3587            search_filter: String::new(),
3588            state_filter: None,
3589            sort_by_distance: false,
3590            sort_by_memory: false,
3591            show_only_visible: false,
3592            timeline_scroll_x: 0.0,
3593            timeline_zoom: 1.0,
3594            map_scroll: Vec2::ZERO,
3595            map_zoom: 1.0,
3596            is_editing_volume: false,
3597            editing_volume_id: None,
3598            simulation_panel_open: false,
3599            budget_panel_open: false,
3600            dep_graph_panel_open: false,
3601        }
3602    }
3603
3604    pub fn select_level(&mut self, id: u64, multi_select: bool) {
3605        if !multi_select {
3606            self.selected_level_ids.clear();
3607        }
3608        self.selected_level_ids.insert(id);
3609    }
3610
3611    pub fn deselect_level(&mut self, id: u64) {
3612        self.selected_level_ids.remove(&id);
3613    }
3614
3615    pub fn is_level_selected(&self, id: u64) -> bool {
3616        self.selected_level_ids.contains(&id)
3617    }
3618
3619    pub fn filtered_levels<'a>(&'a self, levels: &'a [StreamingLevel]) -> Vec<&'a StreamingLevel> {
3620        let mut result: Vec<&StreamingLevel> = levels.iter()
3621            .filter(|l| {
3622                let name_match = self.search_filter.is_empty()
3623                    || l.name.to_lowercase().contains(&self.search_filter.to_lowercase());
3624                let state_match = self.state_filter.map_or(true, |s| l.state == s);
3625                let visibility_match = !self.show_only_visible || l.is_visible;
3626                name_match && state_match && visibility_match
3627            })
3628            .collect();
3629
3630        if self.sort_by_distance {
3631            result.sort_by(|a, b| a.distance_to_camera.partial_cmp(&b.distance_to_camera)
3632                .unwrap_or(std::cmp::Ordering::Equal));
3633        } else if self.sort_by_memory {
3634            result.sort_by(|a, b| b.memory_footprint_mb.partial_cmp(&a.memory_footprint_mb)
3635                .unwrap_or(std::cmp::Ordering::Equal));
3636        }
3637
3638        result
3639    }
3640}
3641
3642// ============================================================
3643// GRID SPATIAL HASH (ADVANCED)
3644// ============================================================
3645
3646#[derive(Debug)]
3647pub struct SpatialHashGrid {
3648    pub bucket_size: f32,
3649    pub buckets: HashMap<(i32, i32, i32), Vec<u64>>,
3650}
3651
3652impl SpatialHashGrid {
3653    pub fn new(bucket_size: f32) -> Self {
3654        Self { bucket_size, buckets: HashMap::new() }
3655    }
3656
3657    fn hash_pos(&self, pos: Vec3) -> (i32, i32, i32) {
3658        (
3659            (pos.x / self.bucket_size).floor() as i32,
3660            (pos.y / self.bucket_size).floor() as i32,
3661            (pos.z / self.bucket_size).floor() as i32,
3662        )
3663    }
3664
3665    pub fn insert(&mut self, id: u64, pos: Vec3) {
3666        let h = self.hash_pos(pos);
3667        self.buckets.entry(h).or_default().push(id);
3668    }
3669
3670    pub fn remove(&mut self, id: u64, pos: Vec3) {
3671        let h = self.hash_pos(pos);
3672        if let Some(v) = self.buckets.get_mut(&h) {
3673            v.retain(|&i| i != id);
3674        }
3675    }
3676
3677    pub fn query_radius(&self, pos: Vec3, radius: f32) -> Vec<u64> {
3678        let r = (radius / self.bucket_size).ceil() as i32 + 1;
3679        let h = self.hash_pos(pos);
3680        let mut result = Vec::new();
3681        for dz in -r..=r {
3682            for dy in -r..=r {
3683                for dx in -r..=r {
3684                    let key = (h.0 + dx, h.1 + dy, h.2 + dz);
3685                    if let Some(v) = self.buckets.get(&key) {
3686                        result.extend_from_slice(v);
3687                    }
3688                }
3689            }
3690        }
3691        result
3692    }
3693
3694    pub fn clear(&mut self) {
3695        self.buckets.clear();
3696    }
3697
3698    pub fn total_entries(&self) -> usize {
3699        self.buckets.values().map(|v| v.len()).sum()
3700    }
3701}
3702
3703// ============================================================
3704// LEVEL TRANSITION CONTROLLER
3705// ============================================================
3706
3707#[derive(Debug, Clone)]
3708pub struct LevelTransition {
3709    pub from_level_id: u64,
3710    pub to_level_id: u64,
3711    pub transition_type: SectorTransitionType,
3712    pub progress: f32,   // 0..1
3713    pub duration_s: f32,
3714    pub is_complete: bool,
3715}
3716
3717impl LevelTransition {
3718    pub fn new(from: u64, to: u64, transition_type: SectorTransitionType, duration_s: f32) -> Self {
3719        Self {
3720            from_level_id: from,
3721            to_level_id: to,
3722            transition_type,
3723            progress: 0.0,
3724            duration_s,
3725            is_complete: false,
3726        }
3727    }
3728
3729    pub fn update(&mut self, dt_s: f32) {
3730        if self.is_complete { return; }
3731        self.progress += dt_s / self.duration_s.max(0.001);
3732        if self.progress >= 1.0 {
3733            self.progress = 1.0;
3734            self.is_complete = true;
3735        }
3736    }
3737
3738    pub fn fade_alpha(&self) -> f32 {
3739        match self.transition_type {
3740            SectorTransitionType::Fade => {
3741                if self.progress < 0.5 {
3742                    self.progress * 2.0
3743                } else {
3744                    (1.0 - self.progress) * 2.0
3745                }
3746            }
3747            SectorTransitionType::Immediate => 0.0,
3748            _ => 1.0 - self.progress,
3749        }
3750    }
3751
3752    pub fn smoothed_progress(&self) -> f32 {
3753        // Smoothstep
3754        let t = self.progress;
3755        t * t * (3.0 - 2.0 * t)
3756    }
3757}
3758
3759#[derive(Debug)]
3760pub struct TransitionController {
3761    pub active_transitions: Vec<LevelTransition>,
3762    pub completed_transitions: VecDeque<LevelTransition>,
3763}
3764
3765impl TransitionController {
3766    pub fn new() -> Self {
3767        Self {
3768            active_transitions: Vec::new(),
3769            completed_transitions: VecDeque::new(),
3770        }
3771    }
3772
3773    pub fn begin_transition(&mut self, from: u64, to: u64, t: SectorTransitionType, duration_s: f32) {
3774        self.active_transitions.push(LevelTransition::new(from, to, t, duration_s));
3775    }
3776
3777    pub fn update(&mut self, dt_s: f32) {
3778        let mut to_complete = Vec::new();
3779        for (i, t) in self.active_transitions.iter_mut().enumerate() {
3780            t.update(dt_s);
3781            if t.is_complete { to_complete.push(i); }
3782        }
3783        for i in to_complete.into_iter().rev() {
3784            let t = self.active_transitions.remove(i);
3785            if self.completed_transitions.len() >= 64 { self.completed_transitions.pop_front(); }
3786            self.completed_transitions.push_back(t);
3787        }
3788    }
3789
3790    pub fn is_transitioning(&self) -> bool {
3791        !self.active_transitions.is_empty()
3792    }
3793
3794    pub fn get_fade_alpha(&self, level_id: u64) -> f32 {
3795        let mut alpha = 1.0f32;
3796        for t in &self.active_transitions {
3797            if t.from_level_id == level_id || t.to_level_id == level_id {
3798                alpha = alpha.min(1.0 - t.fade_alpha());
3799            }
3800        }
3801        alpha
3802    }
3803}
3804
3805// ============================================================
3806// CULLING MANAGER
3807// ============================================================
3808
3809#[derive(Debug)]
3810pub struct CullingManager {
3811    pub frustum: Frustum,
3812    pub hzb: HierarchicalZBuffer,
3813    pub occlusion_cache: OcclusionCache,
3814    pub total_tested: u64,
3815    pub total_culled_frustum: u64,
3816    pub total_culled_occlusion: u64,
3817    pub total_passed: u64,
3818}
3819
3820impl CullingManager {
3821    pub fn new(vp_width: usize, vp_height: usize) -> Self {
3822        Self {
3823            frustum: Frustum::from_view_proj(Mat4::IDENTITY),
3824            hzb: HierarchicalZBuffer::new(vp_width / 4, vp_height / 4),
3825            occlusion_cache: OcclusionCache::new(4),
3826            total_tested: 0,
3827            total_culled_frustum: 0,
3828            total_culled_occlusion: 0,
3829            total_passed: 0,
3830        }
3831    }
3832
3833    pub fn update_frustum(&mut self, view_proj: Mat4) {
3834        self.frustum = Frustum::from_view_proj(view_proj);
3835    }
3836
3837    pub fn update_hzb(&mut self, depth_buffer: &[f32]) {
3838        self.hzb.build_from_depth(depth_buffer);
3839    }
3840
3841    pub fn test_level(&mut self, level: &StreamingLevel, view_proj: &Mat4, frame: u64) -> bool {
3842        self.total_tested += 1;
3843
3844        // Frustum test
3845        if !self.frustum.test_aabb(&level.bounds) {
3846            self.total_culled_frustum += 1;
3847            return false;
3848        }
3849
3850        // Occlusion cache check
3851        if let Some(cached_visible) = self.occlusion_cache.get(level.id, frame) {
3852            if !cached_visible {
3853                self.total_culled_occlusion += 1;
3854                return false;
3855            }
3856            self.total_passed += 1;
3857            return true;
3858        }
3859
3860        // HZB test
3861        let hzb_visible = self.hzb.test_aabb_visibility(&level.bounds, view_proj);
3862        self.occlusion_cache.set(level.id, hzb_visible, frame);
3863
3864        if !hzb_visible {
3865            self.total_culled_occlusion += 1;
3866            return false;
3867        }
3868
3869        self.total_passed += 1;
3870        true
3871    }
3872
3873    pub fn cull_efficiency(&self) -> f32 {
3874        if self.total_tested == 0 { return 0.0; }
3875        (self.total_culled_frustum + self.total_culled_occlusion) as f32 / self.total_tested as f32
3876    }
3877
3878    pub fn reset_stats(&mut self) {
3879        self.total_tested = 0;
3880        self.total_culled_frustum = 0;
3881        self.total_culled_occlusion = 0;
3882        self.total_passed = 0;
3883    }
3884}
3885
3886// ============================================================
3887// STREAMING LEVEL EDITOR (EXTENDED)
3888// ============================================================
3889
3890/// Extended version of the editor with all subsystems
3891#[derive(Debug)]
3892pub struct FullLevelStreamingEditor {
3893    pub core: LevelStreamingEditor,
3894    pub instance_manager: LevelInstanceManager,
3895    pub ui_state: LevelStreamingEditorUiState,
3896    pub profiler: LevelStreamingProfiler,
3897    pub transition_controller: TransitionController,
3898    pub culling_manager: CullingManager,
3899    pub spatial_hash: SpatialHashGrid,
3900    pub bandwidth_estimator: BandwidthEstimator,
3901    pub occlusion_cache_ext: OcclusionCache,
3902}
3903
3904impl FullLevelStreamingEditor {
3905    pub fn new(config: LevelStreamingEditorConfig) -> Self {
3906        let vp_w = config.viewport_width as usize;
3907        let vp_h = config.viewport_height as usize;
3908        Self {
3909            core: LevelStreamingEditor::new(config),
3910            instance_manager: LevelInstanceManager::new(),
3911            ui_state: LevelStreamingEditorUiState::new(),
3912            profiler: LevelStreamingProfiler::new(),
3913            transition_controller: TransitionController::new(),
3914            culling_manager: CullingManager::new(vp_w, vp_h),
3915            spatial_hash: SpatialHashGrid::new(DEFAULT_CELL_SIZE),
3916            bandwidth_estimator: BandwidthEstimator::new(50.0),
3917            occlusion_cache_ext: OcclusionCache::new(8),
3918        }
3919    }
3920
3921    pub fn tick(&mut self, dt_s: f32) {
3922        let start_frame = self.core.current_frame;
3923        self.core.tick(dt_s);
3924        self.transition_controller.update(dt_s);
3925        self.culling_manager.update_frustum(self.core.camera_view_proj);
3926        self.occlusion_cache_ext.evict_stale(self.core.current_frame);
3927
3928        let queue_depth = self.core.load_queue.queue_depth();
3929        let memory_mb = self.core.memory_manager.used_mb;
3930        self.profiler.record_frame(dt_s * 1000.0, queue_depth, memory_mb, 0.0, dt_s);
3931    }
3932
3933    pub fn add_level_with_instance(
3934        &mut self,
3935        name: String,
3936        asset: StreamingLevelAsset,
3937        bounds: Aabb,
3938        transform: Mat4,
3939    ) -> (u64, u64) {
3940        let level_id = self.core.add_level(name, asset, bounds);
3941        let inst_id = self.instance_manager.instantiate(level_id, transform);
3942        (level_id, inst_id)
3943    }
3944
3945    pub fn select_level_in_ui(&mut self, id: u64, multi: bool) {
3946        self.ui_state.select_level(id, multi);
3947    }
3948
3949    pub fn set_active_panel(&mut self, panel: EditorPanel) {
3950        self.ui_state.active_panel = panel;
3951    }
3952
3953    pub fn get_profiler_summary(&self) -> String {
3954        format!(
3955            "Avg frame: {:.2}ms | P99: {:.2}ms | Peak mem: {:.1}MB | Max queue: {}",
3956            self.profiler.average_frame_time_ms(),
3957            self.profiler.p99_frame_time_ms(),
3958            self.profiler.peak_memory_mb(),
3959            self.profiler.max_queue_depth(),
3960        )
3961    }
3962
3963    pub fn transition_to_sector(&mut self, from_level: u64, to_level: u64, transition: SectorTransitionType) {
3964        let duration = match transition {
3965            SectorTransitionType::Immediate => 0.0,
3966            SectorTransitionType::Fade => 1.0,
3967            SectorTransitionType::Portal => 0.5,
3968            SectorTransitionType::Teleport => 0.2,
3969        };
3970        self.transition_controller.begin_transition(from_level, to_level, transition, duration);
3971        self.core.force_load_level(to_level);
3972    }
3973
3974    pub fn rebuild_spatial_hash(&mut self) {
3975        self.spatial_hash.clear();
3976        for level in self.core.levels.values() {
3977            self.spatial_hash.insert(level.id, level.bounds.center());
3978        }
3979    }
3980
3981    pub fn query_levels_frustum_culled(&self) -> Vec<u64> {
3982        self.core.levels.values()
3983            .filter(|l| l.is_frustum_culled)
3984            .map(|l| l.id)
3985            .collect()
3986    }
3987
3988    pub fn query_levels_occlusion_culled(&self) -> Vec<u64> {
3989        self.core.levels.values()
3990            .filter(|l| l.is_occlusion_culled)
3991            .map(|l| l.id)
3992            .collect()
3993    }
3994
3995    pub fn estimate_load_order_time_ms(&self) -> f32 {
3996        let plan = self.core.dependency_graph.parallel_load_plan();
3997        let bandwidth = self.bandwidth_estimator.estimate();
3998        let mut total_ms = 0.0f32;
3999        for batch in plan {
4000            // Batch loads in parallel — time is the max of the batch
4001            let batch_time_ms = batch.iter()
4002                .filter_map(|&lid| self.core.levels.get(&lid))
4003                .map(|l| self.bandwidth_estimator.estimated_load_time_ms(
4004                    l.asset.size_bytes as f32 / (1024.0 * 1024.0)
4005                ))
4006                .fold(0.0f32, f32::max);
4007            total_ms += batch_time_ms;
4008        }
4009        total_ms
4010    }
4011}
4012
4013// ============================================================
4014// EDITOR COMMAND SYSTEM (UNDO/REDO)
4015// ============================================================
4016
4017#[derive(Debug, Clone)]
4018pub enum StreamingEditorCommand {
4019    SetLoadDistance { level_id: u64, old: f32, new: f32 },
4020    SetUnloadDistance { level_id: u64, old: f32, new: f32 },
4021    SetPriority { level_id: u64, old: LoadPriority, new: LoadPriority },
4022    SetPersistence { level_id: u64, old: LevelPersistence, new: LevelPersistence },
4023    AddDependency { from: u64, to: u64, edge: DependencyEdgeType },
4024    RemoveDependency { from: u64, to: u64 },
4025    MoveLevelBounds { level_id: u64, old_bounds: Aabb, new_bounds: Aabb },
4026    AddVolume { volume_id: u64 },
4027    RemoveVolume { volume_id: u64 },
4028    SetMemoryBudget { old: f32, new: f32 },
4029}
4030
4031#[derive(Debug)]
4032pub struct CommandHistory {
4033    pub undo_stack: Vec<StreamingEditorCommand>,
4034    pub redo_stack: Vec<StreamingEditorCommand>,
4035    pub max_history: usize,
4036}
4037
4038impl CommandHistory {
4039    pub fn new(max_history: usize) -> Self {
4040        Self {
4041            undo_stack: Vec::new(),
4042            redo_stack: Vec::new(),
4043            max_history,
4044        }
4045    }
4046
4047    pub fn push(&mut self, cmd: StreamingEditorCommand) {
4048        self.redo_stack.clear();
4049        if self.undo_stack.len() >= self.max_history {
4050            self.undo_stack.remove(0);
4051        }
4052        self.undo_stack.push(cmd);
4053    }
4054
4055    pub fn undo(&mut self) -> Option<StreamingEditorCommand> {
4056        if let Some(cmd) = self.undo_stack.pop() {
4057            self.redo_stack.push(cmd.clone());
4058            Some(cmd)
4059        } else {
4060            None
4061        }
4062    }
4063
4064    pub fn redo(&mut self) -> Option<StreamingEditorCommand> {
4065        if let Some(cmd) = self.redo_stack.pop() {
4066            self.undo_stack.push(cmd.clone());
4067            Some(cmd)
4068        } else {
4069            None
4070        }
4071    }
4072
4073    pub fn can_undo(&self) -> bool { !self.undo_stack.is_empty() }
4074    pub fn can_redo(&self) -> bool { !self.redo_stack.is_empty() }
4075    pub fn clear(&mut self) { self.undo_stack.clear(); self.redo_stack.clear(); }
4076}
4077
4078pub fn apply_streaming_command(editor: &mut FullLevelStreamingEditor, cmd: &StreamingEditorCommand) {
4079    match cmd {
4080        StreamingEditorCommand::SetLoadDistance { level_id, new, .. } => {
4081            if let Some(l) = editor.core.levels.get_mut(level_id) {
4082                l.load_distance = *new;
4083            }
4084        }
4085        StreamingEditorCommand::SetUnloadDistance { level_id, new, .. } => {
4086            if let Some(l) = editor.core.levels.get_mut(level_id) {
4087                l.unload_distance = *new;
4088            }
4089        }
4090        StreamingEditorCommand::SetPriority { level_id, new, .. } => {
4091            if let Some(l) = editor.core.levels.get_mut(level_id) {
4092                l.priority = *new;
4093            }
4094        }
4095        StreamingEditorCommand::SetPersistence { level_id, new, .. } => {
4096            if let Some(l) = editor.core.levels.get_mut(level_id) {
4097                l.persistence = *new;
4098            }
4099        }
4100        StreamingEditorCommand::AddDependency { from, to, edge } => {
4101            editor.core.dependency_graph.add_dependency(*from, *to, *edge);
4102        }
4103        StreamingEditorCommand::RemoveDependency { from, to } => {
4104            if let Some(node) = editor.core.dependency_graph.nodes.get_mut(from) {
4105                node.dependencies.retain(|&d| d != *to);
4106                node.edge_types.remove(to);
4107            }
4108        }
4109        StreamingEditorCommand::MoveLevelBounds { level_id, new_bounds, .. } => {
4110            if let Some(l) = editor.core.levels.get_mut(level_id) {
4111                l.bounds = new_bounds.clone();
4112                l.sphere_bounds = Sphere::new(new_bounds.center(), new_bounds.extents().length());
4113            }
4114        }
4115        StreamingEditorCommand::AddVolume { .. } => {}
4116        StreamingEditorCommand::RemoveVolume { volume_id } => {
4117            editor.core.volumes.remove(volume_id);
4118        }
4119        StreamingEditorCommand::SetMemoryBudget { new, .. } => {
4120            editor.core.set_memory_budget(*new);
4121        }
4122    }
4123}
4124
4125pub fn undo_streaming_command(editor: &mut FullLevelStreamingEditor, cmd: &StreamingEditorCommand) {
4126    match cmd {
4127        StreamingEditorCommand::SetLoadDistance { level_id, old, .. } => {
4128            if let Some(l) = editor.core.levels.get_mut(level_id) {
4129                l.load_distance = *old;
4130            }
4131        }
4132        StreamingEditorCommand::SetUnloadDistance { level_id, old, .. } => {
4133            if let Some(l) = editor.core.levels.get_mut(level_id) {
4134                l.unload_distance = *old;
4135            }
4136        }
4137        StreamingEditorCommand::SetPriority { level_id, old, .. } => {
4138            if let Some(l) = editor.core.levels.get_mut(level_id) {
4139                l.priority = *old;
4140            }
4141        }
4142        StreamingEditorCommand::SetPersistence { level_id, old, .. } => {
4143            if let Some(l) = editor.core.levels.get_mut(level_id) {
4144                l.persistence = *old;
4145            }
4146        }
4147        StreamingEditorCommand::AddDependency { from, to, .. } => {
4148            if let Some(node) = editor.core.dependency_graph.nodes.get_mut(from) {
4149                node.dependencies.retain(|&d| d != *to);
4150            }
4151        }
4152        StreamingEditorCommand::SetMemoryBudget { old, .. } => {
4153            editor.core.set_memory_budget(*old);
4154        }
4155        _ => {}
4156    }
4157}
4158
4159// ============================================================
4160// LEVEL STREAMING SETTINGS PANEL
4161// ============================================================
4162
4163#[derive(Debug, Clone)]
4164pub struct LevelStreamingSettingsPanel {
4165    pub show_advanced: bool,
4166    pub pending_budget_mb: f32,
4167    pub pending_cell_size: f32,
4168    pub pending_load_dist: f32,
4169    pub pending_unload_dist: f32,
4170    pub pending_max_loads: usize,
4171    pub pending_prefetch: bool,
4172    pub pending_eviction: EvictionPolicy,
4173    pub pending_frustum_cull: bool,
4174    pub pending_occlusion_cull: bool,
4175    pub is_dirty: bool,
4176}
4177
4178impl LevelStreamingSettingsPanel {
4179    pub fn new(config: &LevelStreamingEditorConfig) -> Self {
4180        Self {
4181            show_advanced: false,
4182            pending_budget_mb: config.memory_budget_mb,
4183            pending_cell_size: config.default_cell_size,
4184            pending_load_dist: config.default_load_distance,
4185            pending_unload_dist: config.default_unload_distance,
4186            pending_max_loads: config.max_concurrent_loads,
4187            pending_prefetch: config.enable_prefetch,
4188            pending_eviction: config.eviction_policy,
4189            pending_frustum_cull: config.enable_frustum_culling,
4190            pending_occlusion_cull: config.enable_occlusion_culling,
4191            is_dirty: false,
4192        }
4193    }
4194
4195    pub fn set_budget_mb(&mut self, v: f32) {
4196        self.pending_budget_mb = v.max(64.0);
4197        self.is_dirty = true;
4198    }
4199
4200    pub fn set_load_distance(&mut self, v: f32) {
4201        self.pending_load_dist = v.max(10.0);
4202        if self.pending_unload_dist < self.pending_load_dist {
4203            self.pending_unload_dist = self.pending_load_dist + STREAMING_HYSTERESIS;
4204        }
4205        self.is_dirty = true;
4206    }
4207
4208    pub fn apply_to_config(&mut self, config: &mut LevelStreamingEditorConfig) {
4209        config.memory_budget_mb = self.pending_budget_mb;
4210        config.default_cell_size = self.pending_cell_size;
4211        config.default_load_distance = self.pending_load_dist;
4212        config.default_unload_distance = self.pending_unload_dist;
4213        config.max_concurrent_loads = self.pending_max_loads;
4214        config.enable_prefetch = self.pending_prefetch;
4215        config.eviction_policy = self.pending_eviction;
4216        config.enable_frustum_culling = self.pending_frustum_cull;
4217        config.enable_occlusion_culling = self.pending_occlusion_cull;
4218        self.is_dirty = false;
4219    }
4220
4221    pub fn has_unsaved_changes(&self) -> bool { self.is_dirty }
4222}
4223
4224// ============================================================
4225// STREAMING HEAT MAP
4226// ============================================================
4227
4228#[derive(Debug)]
4229pub struct StreamingHeatMap {
4230    pub grid_w: usize,
4231    pub grid_h: usize,
4232    pub cell_size: f32,
4233    pub origin: Vec2,
4234    pub data: Vec<f32>,     // normalized heat 0..1
4235    pub raw_counts: Vec<u32>, // load/unload events per cell
4236}
4237
4238impl StreamingHeatMap {
4239    pub fn new(grid_w: usize, grid_h: usize, cell_size: f32, origin: Vec2) -> Self {
4240        let n = grid_w * grid_h;
4241        Self {
4242            grid_w,
4243            grid_h,
4244            cell_size,
4245            origin,
4246            data: vec![0.0; n],
4247            raw_counts: vec![0; n],
4248        }
4249    }
4250
4251    pub fn world_to_cell(&self, world_x: f32, world_z: f32) -> Option<(usize, usize)> {
4252        let cx = ((world_x - self.origin.x) / self.cell_size) as isize;
4253        let cz = ((world_z - self.origin.y) / self.cell_size) as isize;
4254        if cx >= 0 && cx < self.grid_w as isize && cz >= 0 && cz < self.grid_h as isize {
4255            Some((cx as usize, cz as usize))
4256        } else {
4257            None
4258        }
4259    }
4260
4261    pub fn record_event(&mut self, world_x: f32, world_z: f32) {
4262        if let Some((cx, cz)) = self.world_to_cell(world_x, world_z) {
4263            self.raw_counts[cz * self.grid_w + cx] += 1;
4264        }
4265    }
4266
4267    pub fn normalize(&mut self) {
4268        let max = self.raw_counts.iter().copied().max().unwrap_or(1).max(1) as f32;
4269        for (i, &count) in self.raw_counts.iter().enumerate() {
4270            self.data[i] = count as f32 / max;
4271        }
4272    }
4273
4274    pub fn decay(&mut self, factor: f32) {
4275        for c in &mut self.raw_counts {
4276            *c = (*c as f32 * factor) as u32;
4277        }
4278        self.normalize();
4279    }
4280
4281    pub fn sample(&self, world_x: f32, world_z: f32) -> f32 {
4282        self.world_to_cell(world_x, world_z)
4283            .map(|(cx, cz)| self.data[cz * self.grid_w + cx])
4284            .unwrap_or(0.0)
4285    }
4286
4287    pub fn peak_cell(&self) -> Option<(usize, usize)> {
4288        let (idx, _) = self.data.iter()
4289            .enumerate()
4290            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))?;
4291        Some((idx % self.grid_w, idx / self.grid_w))
4292    }
4293}
4294
4295// ============================================================
4296// AI SPAWN AWARENESS
4297// ============================================================
4298
4299#[derive(Debug, Clone)]
4300pub struct AiSpawnRequest {
4301    pub spawn_id: u64,
4302    pub type_id: u32,
4303    pub preferred_sector: Option<u64>,
4304    pub spawn_position: Option<Vec3>,
4305    pub count: u32,
4306    pub priority: u32,
4307}
4308
4309#[derive(Debug)]
4310pub struct SectorAwareAiSpawner {
4311    pub pending_requests: VecDeque<AiSpawnRequest>,
4312    pub active_spawns: HashMap<u64, Vec<u64>>, // sector_id -> [spawn_ids]
4313    pub max_per_sector: u32,
4314}
4315
4316impl SectorAwareAiSpawner {
4317    pub fn new(max_per_sector: u32) -> Self {
4318        Self {
4319            pending_requests: VecDeque::new(),
4320            active_spawns: HashMap::new(),
4321            max_per_sector,
4322        }
4323    }
4324
4325    pub fn request_spawn(&mut self, req: AiSpawnRequest) {
4326        self.pending_requests.push_back(req);
4327    }
4328
4329    pub fn process_requests(&mut self, sector_graph: &SectorGraph, camera_pos: Vec3) {
4330        let mut processed = Vec::new();
4331        for (i, req) in self.pending_requests.iter().enumerate() {
4332            let target_sector = req.preferred_sector
4333                .or_else(|| sector_graph.find_sector_at(camera_pos));
4334            let Some(sector_id) = target_sector else { continue };
4335            let current_count = self.active_spawns.get(&sector_id).map(|v| v.len()).unwrap_or(0) as u32;
4336            if current_count + req.count > self.max_per_sector { continue; }
4337            if let Some(sector) = sector_graph.sectors.get(&sector_id) {
4338                if sector.level_ids.is_empty() { continue; }
4339                // Spawn at nearest waypoint if no position given
4340                let spawn_pos = req.spawn_position.or_else(|| {
4341                    sector.nearest_waypoint(camera_pos).map(|w| w.position)
4342                });
4343                if spawn_pos.is_some() {
4344                    let entry = self.active_spawns.entry(sector_id).or_default();
4345                    for _ in 0..req.count {
4346                        entry.push(req.spawn_id);
4347                    }
4348                    processed.push(i);
4349                }
4350            }
4351        }
4352        for i in processed.into_iter().rev() {
4353            self.pending_requests.remove(i);
4354        }
4355    }
4356
4357    pub fn total_active_spawns(&self) -> usize {
4358        self.active_spawns.values().map(|v| v.len()).sum()
4359    }
4360
4361    pub fn clear_sector(&mut self, sector_id: u64) {
4362        self.active_spawns.remove(&sector_id);
4363    }
4364}
4365
4366// ============================================================
4367// STREAMING LOD ANALYSIS
4368// ============================================================
4369
4370#[derive(Debug, Clone)]
4371pub struct LodAnalysisResult {
4372    pub level_id: u64,
4373    pub recommended_lod: LodLevel,
4374    pub current_lod: LodLevel,
4375    pub lod_mismatch: bool,
4376    pub potential_memory_save_mb: f32,
4377    pub screen_size_px: f32,
4378    pub distance_m: f32,
4379}
4380
4381pub fn analyze_lod_distribution(
4382    levels: &[StreamingLevel],
4383    budget_manager: &CombinedBudgetManager,
4384) -> Vec<LodAnalysisResult> {
4385    let global_bias = budget_manager.compute_global_lod_bias();
4386    levels.iter().map(|level| {
4387        let recommended = budget_manager.optimal_lod_for_budget(level.id, level.distance_to_camera);
4388        let current_mem = level.memory_estimate_mb();
4389        let rec_mem = {
4390            let mut tmp = level.clone();
4391            tmp.current_lod = recommended;
4392            tmp.memory_estimate_mb()
4393        };
4394        LodAnalysisResult {
4395            level_id: level.id,
4396            recommended_lod: recommended,
4397            current_lod: level.current_lod,
4398            lod_mismatch: recommended != level.current_lod,
4399            potential_memory_save_mb: (current_mem - rec_mem).max(0.0),
4400            screen_size_px: level.screen_size,
4401            distance_m: level.distance_to_camera,
4402        }
4403    }).collect()
4404}
4405
4406pub fn compute_streaming_importance(
4407    level: &StreamingLevel,
4408    camera_pos: Vec3,
4409    camera_dir: Vec3,
4410    time_since_last_load_s: f32,
4411) -> f32 {
4412    let dist = level.bounds.distance_to_point(camera_pos).max(0.01);
4413
4414    // Distance factor: closer is more important
4415    let dist_factor = 1.0 / (1.0 + dist * 0.001);
4416
4417    // Directional factor: more important if in camera view direction
4418    let to_level = (level.bounds.center() - camera_pos).normalize_or_zero();
4419    let dir_factor = (camera_dir.dot(to_level) * 0.5 + 0.5).powf(2.0);
4420
4421    // Recency factor: levels not recently loaded get a small bump
4422    let recency = (1.0 - (-time_since_last_load_s * 0.1).exp()) * 0.2;
4423
4424    // Screen size factor
4425    let screen_factor = level.screen_size.clamp(0.0, 1.0);
4426
4427    // Importance weight from user
4428    let user_weight = level.importance_weight;
4429
4430    (dist_factor * dir_factor + recency + screen_factor * 0.3) * user_weight
4431}
4432
4433pub fn compute_cell_load_radius(
4434    camera_velocity: Vec3,
4435    base_radius: f32,
4436    lookahead_s: f32,
4437) -> f32 {
4438    let speed = camera_velocity.length();
4439    // Expand radius in velocity direction proportional to speed
4440    base_radius + speed * lookahead_s
4441}
4442
4443pub fn distance_based_lod_bias(distance: f32, budget_pressure: f32) -> f32 {
4444    // Base LOD bias from budget pressure
4445    let budget_bias = budget_pressure * 3.0;
4446    // Additional bias from distance
4447    let dist_bias = (distance * LOD_BIAS_DISTANCE_SCALE).powf(1.5);
4448    budget_bias + dist_bias
4449}
4450
4451pub fn hysteresis_check_load(dist: f32, load_dist: f32, hysteresis: f32) -> bool {
4452    dist < load_dist - hysteresis
4453}
4454
4455pub fn hysteresis_check_unload(dist: f32, unload_dist: f32, hysteresis: f32) -> bool {
4456    dist > unload_dist + hysteresis
4457}
4458
4459pub fn memory_mb_to_bytes(mb: f32) -> u64 {
4460    (mb * 1024.0 * 1024.0) as u64
4461}
4462
4463pub fn bytes_to_memory_mb(bytes: u64) -> f32 {
4464    bytes as f32 / (1024.0 * 1024.0)
4465}
4466
4467pub fn compute_sector_portal_visibility(
4468    portal: &Portal,
4469    camera_pos: Vec3,
4470    camera_dir: Vec3,
4471) -> f32 {
4472    if !portal.is_open { return 0.0; }
4473    let to_portal = portal.center - camera_pos;
4474    let dist = to_portal.length();
4475    if dist < 1e-4 { return 1.0; }
4476    let norm = to_portal / dist;
4477    let dot_dir = camera_dir.dot(norm).max(0.0);
4478    let dot_normal = (-portal.normal).dot(norm).max(0.0);
4479    dot_dir * dot_normal * portal.transmission * (1.0 / (1.0 + dist * 0.001))
4480}
4481
4482pub fn compute_level_bandwidth_estimate_mb_s(
4483    file_size_bytes: u64,
4484    load_time_ms: f32,
4485) -> f32 {
4486    if load_time_ms <= 0.0 { return 0.0; }
4487    let mb = file_size_bytes as f32 / (1024.0 * 1024.0);
4488    mb / (load_time_ms / 1000.0)
4489}
4490
4491pub fn priority_score_for_distance(
4492    dist: f32,
4493    base_priority: LoadPriority,
4494    importance: f32,
4495) -> f32 {
4496    let p = match base_priority {
4497        LoadPriority::Critical => 10000.0,
4498        LoadPriority::High => 1000.0,
4499        LoadPriority::Medium => 100.0,
4500        LoadPriority::Low => 10.0,
4501        LoadPriority::Prefetch => 1.0,
4502    };
4503    let d = (1000.0 / dist.max(1.0)).min(100.0);
4504    p * importance + d
4505}
4506
4507// ============================================================
4508// TESTS / EXAMPLES
4509// ============================================================
4510
4511#[cfg(test)]
4512mod tests {
4513    use super::*;
4514
4515    fn make_asset(id: u64, size_bytes: u64) -> StreamingLevelAsset {
4516        StreamingLevelAsset {
4517            id,
4518            name: format!("Asset_{}", id),
4519            file_path: format!("content/levels/level_{}.pak", id),
4520            size_bytes,
4521            uncompressed_size_bytes: size_bytes * 2,
4522            dependencies: Vec::new(),
4523            load_time_estimate_ms: 200.0,
4524        }
4525    }
4526
4527    #[test]
4528    fn test_aabb_distance() {
4529        let aabb = Aabb::new(Vec3::ZERO, Vec3::splat(10.0));
4530        assert!((aabb.distance_to_point(Vec3::splat(5.0)) - 0.0).abs() < 1e-5);
4531        let dist = aabb.distance_to_point(Vec3::new(20.0, 5.0, 5.0));
4532        assert!((dist - 10.0).abs() < 1e-4);
4533    }
4534
4535    #[test]
4536    fn test_frustum_culling() {
4537        // Simple perspective-like matrix
4538        let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, 1.0, 0.1, 1000.0);
4539        let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 10.0), Vec3::ZERO, Vec3::Y);
4540        let vp = proj * view;
4541        let frustum = Frustum::from_view_proj(vp);
4542        let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
4543        // Center of world should be visible
4544        let _ = frustum.test_aabb(&aabb);
4545    }
4546
4547    #[test]
4548    fn test_dependency_cycle() {
4549        let mut graph = DependencyGraph::new();
4550        graph.add_dependency(1, 2, DependencyEdgeType::HardDependency);
4551        graph.add_dependency(2, 3, DependencyEdgeType::HardDependency);
4552        graph.add_dependency(3, 1, DependencyEdgeType::HardDependency);
4553        assert!(graph.detect_cycles().is_some());
4554    }
4555
4556    #[test]
4557    fn test_dependency_no_cycle() {
4558        let mut graph = DependencyGraph::new();
4559        graph.add_dependency(1, 2, DependencyEdgeType::HardDependency);
4560        graph.add_dependency(2, 3, DependencyEdgeType::HardDependency);
4561        assert!(graph.detect_cycles().is_none());
4562    }
4563
4564    #[test]
4565    fn test_memory_manager_eviction() {
4566        let mut mgr = MemoryPressureManager::new(100.0);
4567        mgr.record_load(1, 40.0);
4568        mgr.record_load(2, 30.0);
4569        mgr.record_load(3, 20.0);
4570        assert!(!mgr.is_critical());
4571        mgr.record_load(4, 8.0);
4572        // 98/100 = 98%, over critical threshold
4573        assert!(mgr.is_critical());
4574    }
4575
4576    #[test]
4577    fn test_hzb_build() {
4578        let mut hzb = HierarchicalZBuffer::new(8, 8);
4579        let depth = vec![0.5f32; 64];
4580        hzb.build_from_depth(&depth);
4581        assert!(hzb.mips[0].data.iter().all(|&d| (d - 0.5).abs() < 1e-5));
4582    }
4583
4584    #[test]
4585    fn test_prefetch_prediction() {
4586        let mut predictor = PrefetchPredictor::new(2.0);
4587        let t = 0.0;
4588        predictor.update(Vec3::ZERO, t);
4589        predictor.update(Vec3::new(10.0, 0.0, 0.0), 1000.0);
4590        let predicted = predictor.predicted_camera_pos();
4591        // Moving at 10 units/sec, 2s lookahead => ~20 units ahead
4592        assert!(predicted.x > 15.0);
4593    }
4594
4595    #[test]
4596    fn test_world_partition() {
4597        let mut grid = WorldPartitionGrid::new(512.0, Vec3::ZERO);
4598        let coord = grid.world_to_cell(Vec3::new(256.0, 0.0, 256.0));
4599        assert_eq!(coord, CellCoord::new(0, 0, 0));
4600        let coord2 = grid.world_to_cell(Vec3::new(600.0, 0.0, 600.0));
4601        assert_eq!(coord2, CellCoord::new(1, 1, 0));
4602    }
4603
4604    #[test]
4605    fn test_load_queue_priority() {
4606        let mut queue = StreamingLoadQueue::new(4);
4607        queue.enqueue(LoadRequest {
4608            level_id: 1, priority: LoadPriority::Low,
4609            distance_weight: 0.1, enqueue_time_ms: 0.0,
4610            predicted_load_time_ms: 100.0, is_prefetch: false,
4611        });
4612        queue.enqueue(LoadRequest {
4613            level_id: 2, priority: LoadPriority::Critical,
4614            distance_weight: 0.1, enqueue_time_ms: 0.0,
4615            predicted_load_time_ms: 100.0, is_prefetch: false,
4616        });
4617        let next = queue.dequeue_next().unwrap();
4618        assert_eq!(next.level_id, 2); // Critical should come first
4619    }
4620
4621    #[test]
4622    fn test_streaming_level_lod() {
4623        let asset = make_asset(1, 50 * 1024 * 1024);
4624        let bounds = Aabb::new(Vec3::ZERO, Vec3::splat(100.0));
4625        let level = StreamingLevel::new(1, "Test".into(), asset, bounds);
4626        assert_eq!(level.compute_lod(50.0, 0.0), LodLevel::Lod0);
4627        assert_eq!(level.compute_lod(200.0, 0.0), LodLevel::Lod1);
4628        assert_eq!(level.compute_lod(500.0, 0.0), LodLevel::Lod2);
4629        assert_eq!(level.compute_lod(800.0, 0.0), LodLevel::Lod3);
4630        assert_eq!(level.compute_lod(1500.0, 0.0), LodLevel::Culled);
4631    }
4632
4633    #[test]
4634    fn test_editor_add_level() {
4635        let config = LevelStreamingEditorConfig::default();
4636        let mut editor = LevelStreamingEditor::new(config);
4637        let asset = make_asset(1, 10 * 1024 * 1024);
4638        let bounds = Aabb::new(Vec3::ZERO, Vec3::splat(512.0));
4639        let id = editor.add_level("TestLevel".into(), asset, bounds);
4640        assert!(editor.levels.contains_key(&id));
4641    }
4642
4643    #[test]
4644    fn test_sector_pvs() {
4645        let mut sg = SectorGraph::new();
4646        let s1 = Sector::new(1, "Room1".into(), Aabb::new(Vec3::ZERO, Vec3::splat(10.0)));
4647        let s2 = Sector::new(2, "Room2".into(), Aabb::new(Vec3::new(10.0, 0.0, 0.0), Vec3::new(20.0, 10.0, 10.0)));
4648        sg.add_sector(s1);
4649        sg.add_sector(s2);
4650        let portal = Portal::new(1, 1, 2, Vec3::new(10.0, 5.0, 5.0), Vec3::new(-1.0, 0.0, 0.0), Vec2::new(2.0, 2.0));
4651        sg.add_portal(portal);
4652        // Camera in sector 1
4653        let pvs = sg.compute_pvs(Vec3::new(5.0, 5.0, 5.0), &Mat4::IDENTITY, 4);
4654        assert!(pvs.contains(&1));
4655    }
4656
4657    #[test]
4658    fn test_topological_sort() {
4659        let mut graph = DependencyGraph::new();
4660        graph.add_dependency(3, 2, DependencyEdgeType::HardDependency);
4661        graph.add_dependency(3, 1, DependencyEdgeType::HardDependency);
4662        graph.add_dependency(2, 1, DependencyEdgeType::HardDependency);
4663        let result = graph.topological_sort().expect("no cycle");
4664        // 1 should come before 2 and 3
4665        let pos_1 = result.iter().position(|&x| x == 1).unwrap();
4666        let pos_2 = result.iter().position(|&x| x == 2).unwrap();
4667        let pos_3 = result.iter().position(|&x| x == 3).unwrap();
4668        assert!(pos_1 < pos_2);
4669        assert!(pos_1 < pos_3);
4670    }
4671
4672    #[test]
4673    fn test_command_history() {
4674        let mut hist = CommandHistory::new(10);
4675        hist.push(StreamingEditorCommand::SetMemoryBudget { old: 512.0, new: 1024.0 });
4676        assert!(hist.can_undo());
4677        let cmd = hist.undo().unwrap();
4678        assert!(hist.can_redo());
4679    }
4680
4681    #[test]
4682    fn test_bandwidth_estimator() {
4683        let mut est = BandwidthEstimator::new(50.0);
4684        est.add_sample(100.0);
4685        est.add_sample(100.0);
4686        // EMA should be moving toward 100
4687        assert!(est.estimate() > 50.0);
4688    }
4689
4690    #[test]
4691    fn test_heatmap() {
4692        let mut hm = StreamingHeatMap::new(10, 10, 100.0, Vec2::ZERO);
4693        hm.record_event(50.0, 50.0);
4694        hm.record_event(50.0, 50.0);
4695        hm.normalize();
4696        assert!((hm.sample(50.0, 50.0) - 1.0).abs() < 1e-5);
4697    }
4698
4699    #[test]
4700    fn test_spatial_hash() {
4701        let mut hash = SpatialHashGrid::new(100.0);
4702        hash.insert(1, Vec3::new(50.0, 0.0, 50.0));
4703        hash.insert(2, Vec3::new(1000.0, 0.0, 1000.0));
4704        let results = hash.query_radius(Vec3::new(50.0, 0.0, 50.0), 10.0);
4705        assert!(results.contains(&1));
4706        assert!(!results.contains(&2));
4707    }
4708
4709    #[test]
4710    fn test_sphere_containment() {
4711        let s = Sphere::new(Vec3::ZERO, 10.0);
4712        assert!(s.contains_point(Vec3::new(5.0, 0.0, 0.0)));
4713        assert!(!s.contains_point(Vec3::new(15.0, 0.0, 0.0)));
4714    }
4715
4716    #[test]
4717    fn test_combined_budget() {
4718        let mut mgr = CombinedBudgetManager::new(1000.0);
4719        mgr.update_entry(1, LodLevel::Lod0, 100.0, 10.0);
4720        mgr.update_entry(2, LodLevel::Lod1, 50.0, 5.0);
4721        assert!((mgr.total_usage_mb() - 165.0).abs() < 1e-4);
4722    }
4723}
4724
4725// ============================================================
4726// STREAMING DISTANCE CACHE
4727// ============================================================
4728
4729#[derive(Debug)]
4730pub struct StreamingDistanceCache {
4731    pub distances: HashMap<u64, f32>,
4732    pub last_camera_pos: Vec3,
4733    pub dirty_threshold_sq: f32,
4734    pub frame_updated: u64,
4735}
4736
4737impl StreamingDistanceCache {
4738    pub fn new() -> Self {
4739        Self {
4740            distances: HashMap::new(),
4741            last_camera_pos: Vec3::splat(f32::MAX),
4742            dirty_threshold_sq: 1.0,
4743            frame_updated: 0,
4744        }
4745    }
4746
4747    pub fn update(&mut self, camera_pos: Vec3, levels: &[StreamingLevel], frame: u64) {
4748        let moved_sq = (camera_pos - self.last_camera_pos).length_squared();
4749        if moved_sq < self.dirty_threshold_sq && frame == self.frame_updated {
4750            return;
4751        }
4752        self.last_camera_pos = camera_pos;
4753        self.frame_updated = frame;
4754        for level in levels {
4755            let dist = level.bounds.distance_to_point(camera_pos);
4756            self.distances.insert(level.id, dist);
4757        }
4758    }
4759
4760    pub fn get(&self, level_id: u64) -> Option<f32> {
4761        self.distances.get(&level_id).copied()
4762    }
4763
4764    pub fn invalidate(&mut self) {
4765        self.last_camera_pos = Vec3::splat(f32::MAX);
4766    }
4767
4768    pub fn nearest_level_id(&self) -> Option<u64> {
4769        self.distances.iter()
4770            .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
4771            .map(|(&id, _)| id)
4772    }
4773}
4774
4775// ============================================================
4776// DYNAMIC OBJECT STREAMING TRACKER
4777// ============================================================
4778
4779#[derive(Debug, Clone)]
4780pub struct DynamicStreamingObject {
4781    pub id: u64,
4782    pub name: String,
4783    pub position: Vec3,
4784    pub velocity: Vec3,
4785    pub bounds_radius: f32,
4786    pub current_cell: CellCoord,
4787    pub visible: bool,
4788    pub importance: f32,
4789    pub last_move_time_ms: f64,
4790}
4791
4792impl DynamicStreamingObject {
4793    pub fn new(id: u64, name: String, position: Vec3, bounds_radius: f32) -> Self {
4794        Self {
4795            id,
4796            name,
4797            position,
4798            velocity: Vec3::ZERO,
4799            bounds_radius,
4800            current_cell: CellCoord::new(0, 0, 0),
4801            visible: true,
4802            importance: 1.0,
4803            last_move_time_ms: 0.0,
4804        }
4805    }
4806
4807    pub fn update_position(&mut self, new_pos: Vec3, dt_s: f32, time_ms: f64) {
4808        let delta = new_pos - self.position;
4809        if dt_s > 1e-6 {
4810            self.velocity = delta / dt_s;
4811        }
4812        self.position = new_pos;
4813        if delta.length_squared() > 0.001 {
4814            self.last_move_time_ms = time_ms;
4815        }
4816    }
4817
4818    pub fn predicted_position(&self, lookahead_s: f32) -> Vec3 {
4819        self.position + self.velocity * lookahead_s
4820    }
4821
4822    pub fn bounds_sphere(&self) -> Sphere {
4823        Sphere::new(self.position, self.bounds_radius)
4824    }
4825
4826    pub fn speed(&self) -> f32 { self.velocity.length() }
4827}
4828
4829#[derive(Debug)]
4830pub struct DynamicObjectTracker {
4831    pub objects: HashMap<u64, DynamicStreamingObject>,
4832    pub next_id: u64,
4833    pub grid: WorldPartitionGrid,
4834    pub total_moves: u64,
4835}
4836
4837impl DynamicObjectTracker {
4838    pub fn new(cell_size: f32) -> Self {
4839        Self {
4840            objects: HashMap::new(),
4841            next_id: 1,
4842            grid: WorldPartitionGrid::new(cell_size, Vec3::ZERO),
4843            total_moves: 0,
4844        }
4845    }
4846
4847    pub fn spawn(&mut self, name: String, pos: Vec3, radius: f32) -> u64 {
4848        let id = self.next_id;
4849        self.next_id += 1;
4850        let mut obj = DynamicStreamingObject::new(id, name, pos, radius);
4851        obj.current_cell = self.grid.world_to_cell(pos);
4852        self.grid.register_object(id, pos);
4853        self.objects.insert(id, obj);
4854        id
4855    }
4856
4857    pub fn despawn(&mut self, id: u64) {
4858        if let Some(_obj) = self.objects.remove(&id) {
4859            self.grid.unregister_object(id);
4860        }
4861    }
4862
4863    pub fn update_position(&mut self, id: u64, new_pos: Vec3, dt_s: f32, time_ms: f64) {
4864        if let Some(obj) = self.objects.get_mut(&id) {
4865            let old_cell = obj.current_cell;
4866            obj.update_position(new_pos, dt_s, time_ms);
4867            let new_cell = self.grid.world_to_cell(new_pos);
4868            obj.current_cell = new_cell;
4869            if old_cell != new_cell {
4870                self.grid.register_object(id, new_pos);
4871                self.total_moves += 1;
4872            }
4873        }
4874    }
4875
4876    pub fn query_near(&self, pos: Vec3, radius: f32) -> Vec<u64> {
4877        self.grid.query_objects_in_radius(pos, radius)
4878    }
4879
4880    pub fn objects_in_cell(&self, coord: CellCoord) -> Vec<u64> {
4881        self.grid.cells.get(&coord)
4882            .map(|c| c.dynamic_object_ids.clone())
4883            .unwrap_or_default()
4884    }
4885
4886    pub fn total_objects(&self) -> usize { self.objects.len() }
4887
4888    pub fn objects_by_importance(&self) -> Vec<&DynamicStreamingObject> {
4889        let mut sorted: Vec<&DynamicStreamingObject> = self.objects.values().collect();
4890        sorted.sort_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
4891        sorted
4892    }
4893
4894    pub fn fast_moving_objects(&self, speed_threshold: f32) -> Vec<u64> {
4895        self.objects.iter()
4896            .filter(|(_, o)| o.speed() > speed_threshold)
4897            .map(|(&id, _)| id)
4898            .collect()
4899    }
4900}
4901
4902// ============================================================
4903// LEVEL STREAMING ANALYTICS
4904// ============================================================
4905
4906#[derive(Debug, Clone)]
4907pub struct StreamingAnalyticsSession {
4908    pub session_id: u64,
4909    pub start_time_ms: f64,
4910    pub end_time_ms: f64,
4911    pub total_loads: u32,
4912    pub total_unloads: u32,
4913    pub total_evictions: u32,
4914    pub peak_memory_mb: f32,
4915    pub total_bandwidth_mb: f32,
4916    pub stall_events: u32,
4917    pub average_load_latency_ms: f32,
4918    pub prefetch_hit_rate: f32,
4919    pub unique_levels_loaded: HashSet<u64>,
4920}
4921
4922impl StreamingAnalyticsSession {
4923    pub fn new(session_id: u64, start_ms: f64) -> Self {
4924        Self {
4925            session_id,
4926            start_time_ms: start_ms,
4927            end_time_ms: start_ms,
4928            total_loads: 0,
4929            total_unloads: 0,
4930            total_evictions: 0,
4931            peak_memory_mb: 0.0,
4932            total_bandwidth_mb: 0.0,
4933            stall_events: 0,
4934            average_load_latency_ms: 0.0,
4935            prefetch_hit_rate: 0.0,
4936            unique_levels_loaded: HashSet::new(),
4937        }
4938    }
4939
4940    pub fn record_load(&mut self, level_id: u64, latency_ms: f32, mb: f32) {
4941        self.total_loads += 1;
4942        self.total_bandwidth_mb += mb;
4943        self.unique_levels_loaded.insert(level_id);
4944        let n = self.total_loads as f32;
4945        self.average_load_latency_ms = self.average_load_latency_ms * (n - 1.0) / n + latency_ms / n;
4946    }
4947
4948    pub fn record_unload(&mut self) { self.total_unloads += 1; }
4949    pub fn record_eviction(&mut self) { self.total_evictions += 1; }
4950    pub fn record_stall(&mut self) { self.stall_events += 1; }
4951
4952    pub fn update_peak_memory(&mut self, used_mb: f32) {
4953        self.peak_memory_mb = self.peak_memory_mb.max(used_mb);
4954    }
4955
4956    pub fn duration_s(&self) -> f32 {
4957        ((self.end_time_ms - self.start_time_ms) / 1000.0) as f32
4958    }
4959
4960    pub fn average_bandwidth_mb_s(&self) -> f32 {
4961        let d = self.duration_s();
4962        if d > 0.0 { self.total_bandwidth_mb / d } else { 0.0 }
4963    }
4964
4965    pub fn finalize(&mut self, end_ms: f64) {
4966        self.end_time_ms = end_ms;
4967    }
4968
4969    pub fn efficiency_score(&self) -> f32 {
4970        // Ratio of unique levels loaded vs total loads (high = low redundancy)
4971        if self.total_loads == 0 { return 1.0; }
4972        self.unique_levels_loaded.len() as f32 / self.total_loads as f32
4973    }
4974}
4975
4976// ============================================================
4977// REGION OF INTEREST SYSTEM
4978// ============================================================
4979
4980#[derive(Debug, Clone)]
4981pub struct RegionOfInterest {
4982    pub id: u64,
4983    pub name: String,
4984    pub bounds: Aabb,
4985    pub boost_priority: LoadPriority,
4986    pub boost_load_distance: f32,
4987    pub is_active: bool,
4988    pub activation_condition: String,
4989    pub activation_time_ms: f64,
4990}
4991
4992impl RegionOfInterest {
4993    pub fn new(id: u64, name: String, bounds: Aabb, priority: LoadPriority) -> Self {
4994        Self {
4995            id,
4996            name,
4997            bounds,
4998            boost_priority: priority,
4999            boost_load_distance: 200.0,
5000            is_active: false,
5001            activation_condition: String::new(),
5002            activation_time_ms: 0.0,
5003        }
5004    }
5005
5006    pub fn activate(&mut self, time_ms: f64) {
5007        self.is_active = true;
5008        self.activation_time_ms = time_ms;
5009    }
5010
5011    pub fn deactivate(&mut self) { self.is_active = false; }
5012
5013    pub fn camera_in_range(&self, camera_pos: Vec3, margin: f32) -> bool {
5014        self.is_active && self.bounds.expand_by(margin).contains_point(camera_pos)
5015    }
5016
5017    pub fn overlap_area_with(&self, other: &Aabb) -> f32 {
5018        let ix = (self.bounds.max.x.min(other.max.x) - self.bounds.min.x.max(other.min.x)).max(0.0);
5019        let iy = (self.bounds.max.y.min(other.max.y) - self.bounds.min.y.max(other.min.y)).max(0.0);
5020        let iz = (self.bounds.max.z.min(other.max.z) - self.bounds.min.z.max(other.min.z)).max(0.0);
5021        ix * iy * iz
5022    }
5023}
5024
5025#[derive(Debug)]
5026pub struct RegionOfInterestManager {
5027    pub regions: HashMap<u64, RegionOfInterest>,
5028    pub active_regions: HashSet<u64>,
5029    pub next_id: u64,
5030}
5031
5032impl RegionOfInterestManager {
5033    pub fn new() -> Self {
5034        Self {
5035            regions: HashMap::new(),
5036            active_regions: HashSet::new(),
5037            next_id: 1,
5038        }
5039    }
5040
5041    pub fn add_region(&mut self, name: String, bounds: Aabb, priority: LoadPriority) -> u64 {
5042        let id = self.next_id;
5043        self.next_id += 1;
5044        self.regions.insert(id, RegionOfInterest::new(id, name, bounds, priority));
5045        id
5046    }
5047
5048    pub fn update(&mut self, camera_pos: Vec3, time_ms: f64) {
5049        self.active_regions.clear();
5050        for (id, region) in &mut self.regions {
5051            let in_range = region.bounds.expand_by(region.boost_load_distance).contains_point(camera_pos);
5052            if in_range && !region.is_active {
5053                region.activate(time_ms);
5054            } else if !in_range && region.is_active {
5055                region.deactivate();
5056            }
5057            if region.is_active {
5058                self.active_regions.insert(*id);
5059            }
5060        }
5061    }
5062
5063    pub fn priority_for_level(&self, level: &StreamingLevel) -> LoadPriority {
5064        for &id in &self.active_regions {
5065            if let Some(region) = self.regions.get(&id) {
5066                if region.bounds.intersects(&level.bounds) {
5067                    return region.boost_priority;
5068                }
5069            }
5070        }
5071        level.priority
5072    }
5073
5074    pub fn any_active_near(&self, pos: Vec3, radius: f32) -> bool {
5075        self.active_regions.iter().any(|&id| {
5076            self.regions.get(&id).map_or(false, |r| r.bounds.distance_to_point(pos) <= radius)
5077        })
5078    }
5079}
5080
5081// ============================================================
5082// STREAMING CHECKPOINT SYSTEM
5083// ============================================================
5084
5085#[derive(Debug, Clone)]
5086pub struct StreamingCheckpoint {
5087    pub id: u64,
5088    pub name: String,
5089    pub camera_position: Vec3,
5090    pub camera_direction: Vec3,
5091    pub loaded_level_ids: Vec<u64>,
5092    pub memory_used_mb: f32,
5093    pub timestamp_ms: f64,
5094    pub save_slot: u32,
5095}
5096
5097impl StreamingCheckpoint {
5098    pub fn capture(
5099        id: u64,
5100        name: String,
5101        camera_pos: Vec3,
5102        camera_dir: Vec3,
5103        levels: &[StreamingLevel],
5104        memory_mb: f32,
5105        time_ms: f64,
5106    ) -> Self {
5107        let loaded: Vec<u64> = levels.iter()
5108            .filter(|l| l.state == StreamingState::Loaded)
5109            .map(|l| l.id)
5110            .collect();
5111        Self {
5112            id,
5113            name,
5114            camera_position: camera_pos,
5115            camera_direction: camera_dir,
5116            loaded_level_ids: loaded,
5117            memory_used_mb: memory_mb,
5118            timestamp_ms: time_ms,
5119            save_slot: 0,
5120        }
5121    }
5122
5123    pub fn warm_up_requests(&self) -> Vec<u64> {
5124        self.loaded_level_ids.clone()
5125    }
5126}
5127
5128#[derive(Debug)]
5129pub struct CheckpointManager {
5130    pub checkpoints: HashMap<u64, StreamingCheckpoint>,
5131    pub next_id: u64,
5132    pub auto_checkpoint_interval_ms: f64,
5133    pub last_auto_checkpoint_ms: f64,
5134    pub max_checkpoints: usize,
5135}
5136
5137impl CheckpointManager {
5138    pub fn new() -> Self {
5139        Self {
5140            checkpoints: HashMap::new(),
5141            next_id: 1,
5142            auto_checkpoint_interval_ms: 60_000.0,
5143            last_auto_checkpoint_ms: 0.0,
5144            max_checkpoints: 16,
5145        }
5146    }
5147
5148    pub fn save(
5149        &mut self,
5150        name: String,
5151        camera_pos: Vec3,
5152        camera_dir: Vec3,
5153        levels: &[StreamingLevel],
5154        memory_mb: f32,
5155        time_ms: f64,
5156    ) -> u64 {
5157        let id = self.next_id;
5158        self.next_id += 1;
5159        let cp = StreamingCheckpoint::capture(id, name, camera_pos, camera_dir, levels, memory_mb, time_ms);
5160        if self.checkpoints.len() >= self.max_checkpoints {
5161            if let Some(&oldest_id) = self.checkpoints.keys().next() {
5162                self.checkpoints.remove(&oldest_id);
5163            }
5164        }
5165        self.checkpoints.insert(id, cp);
5166        id
5167    }
5168
5169    pub fn maybe_auto_checkpoint(
5170        &mut self,
5171        camera_pos: Vec3,
5172        camera_dir: Vec3,
5173        levels: &[StreamingLevel],
5174        memory_mb: f32,
5175        time_ms: f64,
5176    ) -> Option<u64> {
5177        if time_ms - self.last_auto_checkpoint_ms >= self.auto_checkpoint_interval_ms {
5178            self.last_auto_checkpoint_ms = time_ms;
5179            Some(self.save("Auto".into(), camera_pos, camera_dir, levels, memory_mb, time_ms))
5180        } else { None }
5181    }
5182
5183    pub fn get_latest(&self) -> Option<&StreamingCheckpoint> {
5184        self.checkpoints.values()
5185            .max_by(|a, b| a.timestamp_ms.partial_cmp(&b.timestamp_ms).unwrap_or(std::cmp::Ordering::Equal))
5186    }
5187
5188    pub fn delete_checkpoint(&mut self, id: u64) -> bool {
5189        self.checkpoints.remove(&id).is_some()
5190    }
5191
5192    pub fn total_saved_level_ids(&self) -> HashSet<u64> {
5193        let mut set = HashSet::new();
5194        for cp in self.checkpoints.values() {
5195            for &id in &cp.loaded_level_ids { set.insert(id); }
5196        }
5197        set
5198    }
5199}
5200
5201// ============================================================
5202// LOD TRANSITION SMOOTHER
5203// ============================================================
5204
5205#[derive(Debug, Clone)]
5206pub struct LodTransition {
5207    pub level_id: u64,
5208    pub from_lod: LodLevel,
5209    pub to_lod: LodLevel,
5210    pub progress: f32,
5211    pub duration_s: f32,
5212    pub blend_distance: f32,
5213}
5214
5215impl LodTransition {
5216    pub fn new(level_id: u64, from: LodLevel, to: LodLevel, duration_s: f32) -> Self {
5217        Self {
5218            level_id,
5219            from_lod: from,
5220            to_lod: to,
5221            progress: 0.0,
5222            duration_s,
5223            blend_distance: 50.0,
5224        }
5225    }
5226
5227    pub fn update_progress(&mut self, dt_s: f32) -> bool {
5228        self.progress += dt_s / self.duration_s.max(0.001);
5229        self.progress >= 1.0
5230    }
5231
5232    pub fn blend_alpha(&self) -> f32 {
5233        let t = self.progress.clamp(0.0, 1.0);
5234        t * t * (3.0 - 2.0 * t) // smooth step
5235    }
5236
5237    pub fn is_complete(&self) -> bool { self.progress >= 1.0 }
5238
5239    pub fn reversed(&self) -> Self {
5240        LodTransition::new(self.level_id, self.to_lod, self.from_lod, self.duration_s)
5241    }
5242}
5243
5244#[derive(Debug)]
5245pub struct LodTransitionManager {
5246    pub transitions: HashMap<u64, LodTransition>,
5247    pub completed: VecDeque<(u64, LodLevel)>,
5248}
5249
5250impl LodTransitionManager {
5251    pub fn new() -> Self {
5252        Self {
5253            transitions: HashMap::new(),
5254            completed: VecDeque::with_capacity(64),
5255        }
5256    }
5257
5258    pub fn begin_transition(&mut self, level_id: u64, from: LodLevel, to: LodLevel, duration_s: f32) {
5259        self.transitions.insert(level_id, LodTransition::new(level_id, from, to, duration_s));
5260    }
5261
5262    pub fn update(&mut self, dt_s: f32) {
5263        let mut to_complete: Vec<u64> = Vec::new();
5264        for (id, t) in &mut self.transitions {
5265            if t.update_progress(dt_s) {
5266                to_complete.push(*id);
5267            }
5268        }
5269        for id in to_complete {
5270            if let Some(t) = self.transitions.remove(&id) {
5271                if self.completed.len() >= 64 { self.completed.pop_front(); }
5272                self.completed.push_back((id, t.to_lod));
5273            }
5274        }
5275    }
5276
5277    pub fn get_blend_alpha(&self, level_id: u64) -> f32 {
5278        self.transitions.get(&level_id).map(|t| t.blend_alpha()).unwrap_or(1.0)
5279    }
5280
5281    pub fn is_transitioning(&self, level_id: u64) -> bool {
5282        self.transitions.contains_key(&level_id)
5283    }
5284
5285    pub fn active_count(&self) -> usize { self.transitions.len() }
5286
5287    pub fn cancel_transition(&mut self, level_id: u64) {
5288        self.transitions.remove(&level_id);
5289    }
5290}
5291
5292// ============================================================
5293// STREAMING FLOW OPTIMIZER
5294// ============================================================
5295
5296pub struct StreamingFlowOptimizer;
5297
5298impl StreamingFlowOptimizer {
5299    pub fn reorder_by_geography(requests: &mut Vec<LoadRequest>, levels: &HashMap<u64, StreamingLevel>) {
5300        requests.sort_by(|a, b| {
5301            let pos_a = levels.get(&a.level_id).map(|l| l.bounds.center()).unwrap_or(Vec3::ZERO);
5302            let pos_b = levels.get(&b.level_id).map(|l| l.bounds.center()).unwrap_or(Vec3::ZERO);
5303            pos_a.x.partial_cmp(&pos_b.x).unwrap_or(std::cmp::Ordering::Equal)
5304                .then(pos_a.z.partial_cmp(&pos_b.z).unwrap_or(std::cmp::Ordering::Equal))
5305        });
5306    }
5307
5308    pub fn optimal_batch_size(bandwidth_mb_s: f32, average_level_mb: f32, target_latency_ms: f32) -> usize {
5309        if average_level_mb <= 0.0 || bandwidth_mb_s <= 0.0 { return 1; }
5310        let load_time_ms = average_level_mb / bandwidth_mb_s * 1000.0;
5311        let batch = (target_latency_ms / load_time_ms).ceil() as usize;
5312        batch.clamp(1, MAX_CONCURRENT_LOADS)
5313    }
5314
5315    pub fn estimate_memory_after_loads(
5316        current_mb: f32,
5317        budget_mb: f32,
5318        loads: &[u64],
5319        levels: &HashMap<u64, StreamingLevel>,
5320    ) -> bool {
5321        let additional: f32 = loads.iter()
5322            .filter_map(|id| levels.get(id))
5323            .map(|l| l.memory_estimate_mb())
5324            .sum();
5325        current_mb + additional <= budget_mb
5326    }
5327
5328    pub fn urgency_score(
5329        level: &StreamingLevel,
5330        camera_pos: Vec3,
5331        camera_vel: Vec3,
5332        time_to_load_ms: f32,
5333    ) -> f32 {
5334        let dist = level.bounds.distance_to_point(camera_pos);
5335        let speed = camera_vel.length();
5336        if speed < 0.1 { return 1.0 / dist.max(0.1); }
5337        let dir = camera_vel / speed;
5338        let to_level = (level.bounds.center() - camera_pos).normalize_or_zero();
5339        let dot = dir.dot(to_level).clamp(0.0, 1.0);
5340        let time_to_reach = dist / speed;
5341        let load_time_s = time_to_load_ms / 1000.0;
5342        if time_to_reach < load_time_s { 100.0 }
5343        else { dot * 10.0 / time_to_reach }
5344    }
5345
5346    pub fn speed_adjusted_radius(base_radius: f32, speed_m_s: f32, load_latency_s: f32) -> f32 {
5347        base_radius + speed_m_s * load_latency_s * 1.5
5348    }
5349
5350    pub fn compute_load_stagger_offset(
5351        index: usize,
5352        total: usize,
5353        max_bandwidth_mb_s: f32,
5354        level_size_mb: f32,
5355    ) -> f32 {
5356        if max_bandwidth_mb_s <= 0.0 || total == 0 { return 0.0; }
5357        let load_time = level_size_mb / max_bandwidth_mb_s;
5358        index as f32 * load_time / total as f32
5359    }
5360
5361    pub fn streaming_load_factor(
5362        loading_levels: usize,
5363        max_concurrent: usize,
5364        queue_depth: usize,
5365    ) -> f32 {
5366        let in_flight_factor = loading_levels as f32 / max_concurrent.max(1) as f32;
5367        let queue_factor = (queue_depth as f32 / 16.0).min(1.0);
5368        (in_flight_factor * 0.7 + queue_factor * 0.3).clamp(0.0, 1.0)
5369    }
5370}
5371
5372// ============================================================
5373// SECTOR WAYPOINT PATHFINDER
5374// ============================================================
5375
5376#[derive(Debug)]
5377pub struct SectorWaypointPathfinder {
5378    pub adjacency: HashMap<u64, Vec<u64>>,
5379}
5380
5381impl SectorWaypointPathfinder {
5382    pub fn new(sector_graph: &SectorGraph) -> Self {
5383        let mut adjacency = HashMap::new();
5384        for (id, sector) in &sector_graph.sectors {
5385            adjacency.insert(*id, sector.adjacent_sectors.clone());
5386        }
5387        Self { adjacency }
5388    }
5389
5390    pub fn find_sector_path(&self, start: u64, end: u64) -> Option<Vec<u64>> {
5391        if start == end { return Some(vec![start]); }
5392        let mut visited: HashSet<u64> = HashSet::new();
5393        let mut queue: VecDeque<(u64, Vec<u64>)> = VecDeque::new();
5394        queue.push_back((start, vec![start]));
5395        visited.insert(start);
5396        while let Some((current, path)) = queue.pop_front() {
5397            if let Some(neighbors) = self.adjacency.get(&current) {
5398                for &next in neighbors {
5399                    if next == end {
5400                        let mut full_path = path.clone();
5401                        full_path.push(next);
5402                        return Some(full_path);
5403                    }
5404                    if !visited.contains(&next) {
5405                        visited.insert(next);
5406                        let mut new_path = path.clone();
5407                        new_path.push(next);
5408                        queue.push_back((next, new_path));
5409                    }
5410                }
5411            }
5412        }
5413        None
5414    }
5415
5416    pub fn preload_levels_for_path(&self, path: &[u64], sector_graph: &SectorGraph) -> Vec<u64> {
5417        let mut levels = Vec::new();
5418        for &sector_id in path {
5419            if let Some(sector) = sector_graph.sectors.get(&sector_id) {
5420                for &lid in &sector.level_ids {
5421                    if !levels.contains(&lid) { levels.push(lid); }
5422                }
5423            }
5424        }
5425        levels
5426    }
5427
5428    pub fn estimate_travel_time_s(
5429        &self,
5430        path: &[u64],
5431        sector_graph: &SectorGraph,
5432        speed_m_s: f32,
5433    ) -> f32 {
5434        if path.len() < 2 { return 0.0; }
5435        let mut total_dist = 0.0f32;
5436        for i in 0..(path.len() - 1) {
5437            let a = sector_graph.sectors.get(&path[i]).map(|s| s.bounds.center());
5438            let b = sector_graph.sectors.get(&path[i + 1]).map(|s| s.bounds.center());
5439            if let (Some(a), Some(b)) = (a, b) {
5440                total_dist += (b - a).length();
5441            }
5442        }
5443        if speed_m_s > 0.0 { total_dist / speed_m_s } else { f32::MAX }
5444    }
5445
5446    pub fn reachable_sectors_within_distance(&self, start: u64, max_hops: usize) -> HashSet<u64> {
5447        let mut visited: HashSet<u64> = HashSet::new();
5448        let mut queue: VecDeque<(u64, usize)> = VecDeque::new();
5449        queue.push_back((start, 0));
5450        visited.insert(start);
5451        while let Some((id, depth)) = queue.pop_front() {
5452            if depth >= max_hops { continue; }
5453            if let Some(neighbors) = self.adjacency.get(&id) {
5454                for &next in neighbors {
5455                    if visited.insert(next) {
5456                        queue.push_back((next, depth + 1));
5457                    }
5458                }
5459            }
5460        }
5461        visited
5462    }
5463}
5464
5465// ============================================================
5466// LEVEL ASSET CATALOGUE
5467// ============================================================
5468
5469#[derive(Debug, Clone)]
5470pub struct LevelAssetEntry {
5471    pub asset: StreamingLevelAsset,
5472    pub tags: Vec<String>,
5473    pub last_used_frame: u64,
5474    pub load_count: u32,
5475    pub is_pinned: bool,
5476}
5477
5478impl LevelAssetEntry {
5479    pub fn new(asset: StreamingLevelAsset) -> Self {
5480        Self {
5481            asset,
5482            tags: Vec::new(),
5483            last_used_frame: 0,
5484            load_count: 0,
5485            is_pinned: false,
5486        }
5487    }
5488
5489    pub fn mark_used(&mut self, frame: u64) {
5490        self.last_used_frame = frame;
5491        self.load_count += 1;
5492    }
5493
5494    pub fn size_mb(&self) -> f32 {
5495        self.asset.size_bytes as f32 / (1024.0 * 1024.0)
5496    }
5497}
5498
5499#[derive(Debug)]
5500pub struct LevelAssetCatalogue {
5501    pub entries: HashMap<u64, LevelAssetEntry>,
5502    pub total_size_bytes: u64,
5503    pub tags_index: HashMap<String, Vec<u64>>,
5504}
5505
5506impl LevelAssetCatalogue {
5507    pub fn new() -> Self {
5508        Self {
5509            entries: HashMap::new(),
5510            total_size_bytes: 0,
5511            tags_index: HashMap::new(),
5512        }
5513    }
5514
5515    pub fn register(&mut self, asset: StreamingLevelAsset) {
5516        let id = asset.id;
5517        let size = asset.size_bytes;
5518        self.total_size_bytes += size;
5519        self.entries.insert(id, LevelAssetEntry::new(asset));
5520    }
5521
5522    pub fn tag_asset(&mut self, asset_id: u64, tag: &str) {
5523        if let Some(entry) = self.entries.get_mut(&asset_id) {
5524            if !entry.tags.contains(&tag.to_string()) {
5525                entry.tags.push(tag.to_string());
5526            }
5527        }
5528        self.tags_index.entry(tag.to_string()).or_default().push(asset_id);
5529    }
5530
5531    pub fn assets_by_tag(&self, tag: &str) -> Vec<&LevelAssetEntry> {
5532        self.tags_index.get(tag)
5533            .map(|ids| ids.iter().filter_map(|id| self.entries.get(id)).collect())
5534            .unwrap_or_default()
5535    }
5536
5537    pub fn largest_assets(&self, n: usize) -> Vec<&LevelAssetEntry> {
5538        let mut sorted: Vec<&LevelAssetEntry> = self.entries.values().collect();
5539        sorted.sort_by(|a, b| b.asset.size_bytes.cmp(&a.asset.size_bytes));
5540        sorted.into_iter().take(n).collect()
5541    }
5542
5543    pub fn pin_asset(&mut self, id: u64) {
5544        if let Some(e) = self.entries.get_mut(&id) { e.is_pinned = true; }
5545    }
5546
5547    pub fn unpin_asset(&mut self, id: u64) {
5548        if let Some(e) = self.entries.get_mut(&id) { e.is_pinned = false; }
5549    }
5550
5551    pub fn total_size_mb(&self) -> f32 {
5552        self.total_size_bytes as f32 / (1024.0 * 1024.0)
5553    }
5554
5555    pub fn unpinned_assets_sorted_by_lru(&self, current_frame: u64) -> Vec<u64> {
5556        let mut sorted: Vec<&LevelAssetEntry> = self.entries.values()
5557            .filter(|e| !e.is_pinned)
5558            .collect();
5559        sorted.sort_by_key(|e| e.last_used_frame);
5560        sorted.iter().map(|e| e.asset.id).collect()
5561    }
5562}
5563
5564// ============================================================
5565// STREAMING WORLD COMPOSER
5566// ============================================================
5567
5568#[derive(Debug)]
5569pub struct StreamingWorldComposer {
5570    pub world_name: String,
5571    pub base_level_ids: Vec<u64>,
5572    pub layer_groups: HashMap<String, Vec<u64>>,
5573    pub streaming_sets: HashMap<String, Vec<u64>>,
5574    pub world_bounds: Aabb,
5575    pub camera_start: Vec3,
5576    pub camera_start_dir: Vec3,
5577    pub description: String,
5578}
5579
5580impl StreamingWorldComposer {
5581    pub fn new(world_name: String) -> Self {
5582        Self {
5583            world_name,
5584            base_level_ids: Vec::new(),
5585            layer_groups: HashMap::new(),
5586            streaming_sets: HashMap::new(),
5587            world_bounds: Aabb::new(-Vec3::splat(10000.0), Vec3::splat(10000.0)),
5588            camera_start: Vec3::ZERO,
5589            camera_start_dir: Vec3::NEG_Z,
5590            description: String::new(),
5591        }
5592    }
5593
5594    pub fn add_to_layer(&mut self, layer: &str, level_id: u64) {
5595        self.layer_groups.entry(layer.to_string()).or_default().push(level_id);
5596    }
5597
5598    pub fn create_streaming_set(&mut self, set_name: &str, level_ids: Vec<u64>) {
5599        self.streaming_sets.insert(set_name.to_string(), level_ids);
5600    }
5601
5602    pub fn get_streaming_set(&self, set_name: &str) -> Vec<u64> {
5603        self.streaming_sets.get(set_name).cloned().unwrap_or_default()
5604    }
5605
5606    pub fn levels_in_layer(&self, layer: &str) -> Vec<u64> {
5607        self.layer_groups.get(layer).cloned().unwrap_or_default()
5608    }
5609
5610    pub fn all_managed_level_ids(&self) -> Vec<u64> {
5611        let mut result = self.base_level_ids.clone();
5612        for ids in self.layer_groups.values() {
5613            for &id in ids {
5614                if !result.contains(&id) { result.push(id); }
5615            }
5616        }
5617        result
5618    }
5619
5620    pub fn layer_names(&self) -> Vec<&str> {
5621        self.layer_groups.keys().map(|s| s.as_str()).collect()
5622    }
5623
5624    pub fn set_world_bounds_from_levels(&mut self, levels: &[StreamingLevel]) {
5625        let managed = self.all_managed_level_ids();
5626        let mut merged = Aabb::new(Vec3::splat(f32::MAX), Vec3::splat(f32::MIN));
5627        for l in levels {
5628            if managed.contains(&l.id) {
5629                merged = merged.merge(&l.bounds);
5630            }
5631        }
5632        if merged.min.x <= merged.max.x {
5633            self.world_bounds = merged;
5634        }
5635    }
5636
5637    pub fn count_layers(&self) -> usize { self.layer_groups.len() }
5638}
5639
5640// ============================================================
5641// UTILITY FUNCTIONS
5642// ============================================================
5643
5644pub fn compute_cell_priority_scores(
5645    cells: &mut HashMap<CellCoord, WorldCell>,
5646    camera_pos: Vec3,
5647    camera_dir: Vec3,
5648    budget_pressure: f32,
5649) {
5650    for cell in cells.values_mut() {
5651        let _ = cell.compute_priority_score(camera_pos, camera_dir);
5652        if budget_pressure > 0.8 {
5653            cell.load_priority_score *= 1.0 - (budget_pressure - 0.8) * 2.0;
5654        }
5655    }
5656}
5657
5658pub fn cells_by_priority(cells: &HashMap<CellCoord, WorldCell>, top_n: usize) -> Vec<CellCoord> {
5659    let mut sorted: Vec<(&CellCoord, f32)> = cells.iter()
5660        .map(|(k, v)| (k, v.load_priority_score))
5661        .collect();
5662    sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
5663    sorted.into_iter().take(top_n).map(|(k, _)| *k).collect()
5664}
5665
5666pub fn estimate_level_load_time_ms(
5667    size_bytes: u64,
5668    bandwidth_mb_s: f32,
5669    decompression_factor: f32,
5670) -> f32 {
5671    if bandwidth_mb_s <= 0.0 { return f32::MAX; }
5672    let mb = size_bytes as f32 / (1024.0 * 1024.0);
5673    let io_time_ms = mb / bandwidth_mb_s * 1000.0;
5674    let decomp_time_ms = mb * decompression_factor;
5675    io_time_ms + decomp_time_ms
5676}
5677
5678pub fn lru_eviction_order(
5679    levels: &[StreamingLevel],
5680    lru_order: &VecDeque<u64>,
5681) -> Vec<u64> {
5682    let mut result = Vec::new();
5683    for &id in lru_order.iter() {
5684        if let Some(l) = levels.iter().find(|l| l.id == id) {
5685            if l.state == StreamingState::Loaded && l.persistence != LevelPersistence::AlwaysLoaded {
5686                result.push(id);
5687            }
5688        }
5689    }
5690    result
5691}
5692
5693pub fn should_stream_via_portal(
5694    portal: &Portal,
5695    camera_pos: Vec3,
5696    max_portal_stream_dist: f32,
5697) -> bool {
5698    let dist = (portal.center - camera_pos).length();
5699    portal.is_open && dist < max_portal_stream_dist
5700}
5701
5702pub fn occlusion_cull_sectors(
5703    sectors: &[u64],
5704    visible_pvs: &HashSet<u64>,
5705) -> (Vec<u64>, Vec<u64>) {
5706    let mut visible = Vec::new();
5707    let mut culled = Vec::new();
5708    for &id in sectors {
5709        if visible_pvs.contains(&id) { visible.push(id); } else { culled.push(id); }
5710    }
5711    (visible, culled)
5712}
5713
5714pub fn compute_portal_screen_coverage(portal: &Portal, camera_pos: Vec3, fov_y: f32) -> f32 {
5715    let dist = (portal.center - camera_pos).length().max(0.01);
5716    let angular_h = 2.0 * (portal.half_extents.x / dist).atan();
5717    let angular_v = 2.0 * (portal.half_extents.y / dist).atan();
5718    (angular_h / fov_y).min(1.0) * (angular_v / fov_y).min(1.0)
5719}
5720
5721pub fn compute_streaming_jitter(load_times: &[f32]) -> f32 {
5722    if load_times.len() < 2 { return 0.0; }
5723    let mean = load_times.iter().sum::<f32>() / load_times.len() as f32;
5724    let var = load_times.iter().map(|&t| (t - mean).powi(2)).sum::<f32>() / load_times.len() as f32;
5725    var.sqrt()
5726}
5727
5728pub fn priority_weighted_sort(requests: &mut Vec<LoadRequest>) {
5729    requests.sort_by(|a, b| b.score().partial_cmp(&a.score()).unwrap_or(std::cmp::Ordering::Equal));
5730}
5731
5732pub fn build_adjacency_matrix(sectors: &HashMap<u64, Sector>) -> HashMap<(u64, u64), f32> {
5733    let mut matrix = HashMap::new();
5734    for (id, sector) in sectors {
5735        for &adj_id in &sector.adjacent_sectors {
5736            let a = sector.bounds.center();
5737            let b = sectors.get(&adj_id).map(|s| s.bounds.center()).unwrap_or(Vec3::ZERO);
5738            matrix.insert((*id, adj_id), (b - a).length());
5739        }
5740    }
5741    matrix
5742}
5743
5744pub fn level_memory_breakdown(levels: &[StreamingLevel]) -> HashMap<LodLevel, f32> {
5745    let mut breakdown: HashMap<LodLevel, f32> = HashMap::new();
5746    for level in levels {
5747        if level.state == StreamingState::Loaded {
5748            *breakdown.entry(level.current_lod).or_insert(0.0) += level.memory_footprint_mb;
5749        }
5750    }
5751    breakdown
5752}
5753
5754pub fn sector_coverage_area(sector: &Sector) -> f32 {
5755    let s = sector.bounds.size();
5756    s.x * s.z
5757}
5758
5759pub fn streaming_priority_from_coverage(coverage_ratio: f32, base_priority: LoadPriority) -> LoadPriority {
5760    if coverage_ratio > 0.25 { LoadPriority::Critical }
5761    else if coverage_ratio > 0.1 { LoadPriority::High }
5762    else if coverage_ratio > 0.01 { LoadPriority::Medium }
5763    else { base_priority }
5764}
5765
5766pub fn sector_transition_fade_curve(progress: f32, transition: SectorTransitionType) -> f32 {
5767    match transition {
5768        SectorTransitionType::Immediate => 1.0,
5769        SectorTransitionType::Fade => {
5770            if progress < 0.5 { progress * 2.0 } else { (1.0 - progress) * 2.0 }
5771        }
5772        SectorTransitionType::Portal => { let t = progress; t * t * (3.0 - 2.0 * t) }
5773        SectorTransitionType::Teleport => { if progress < 0.1 || progress > 0.9 { 0.0 } else { 1.0 } }
5774    }
5775}
5776
5777// CellCoord ordering for dedup
5778impl PartialOrd for CellCoord {
5779    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
5780}
5781impl Ord for CellCoord {
5782    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
5783        self.x.cmp(&other.x).then(self.y.cmp(&other.y)).then(self.z.cmp(&other.z))
5784    }
5785}
5786
5787pub fn cells_to_activate(
5788    camera_pos: Vec3,
5789    camera_vel: Vec3,
5790    cell_size: f32,
5791    base_radius: f32,
5792    lookahead_s: f32,
5793    grid_origin: Vec3,
5794) -> Vec<CellCoord> {
5795    let predicted = camera_pos + camera_vel * lookahead_s;
5796    let world_to_coord = |p: Vec3| {
5797        let rel = p - grid_origin;
5798        CellCoord::new((rel.x / cell_size).floor() as i32, (rel.z / cell_size).floor() as i32, (rel.y / cell_size).floor() as i32)
5799    };
5800    let center = world_to_coord(camera_pos);
5801    let pred = world_to_coord(predicted);
5802    let cell_r = (base_radius / cell_size).ceil() as i32 + 1;
5803    let mut coords = Vec::new();
5804    for &base in &[center, pred] {
5805        for dz in -cell_r..=cell_r {
5806            for dx in -cell_r..=cell_r {
5807                coords.push(CellCoord::new(base.x + dx, base.y, base.z + dz));
5808            }
5809        }
5810    }
5811    coords.sort();
5812    coords.dedup();
5813    coords
5814}
5815
5816// ============================================================
5817// FULL STREAMING WORLD MANAGER
5818// ============================================================
5819
5820#[derive(Debug)]
5821pub struct StreamingWorldManager {
5822    pub editor: FullLevelStreamingEditor,
5823    pub dynamic_tracker: DynamicObjectTracker,
5824    pub composer: StreamingWorldComposer,
5825    pub roi_manager: RegionOfInterestManager,
5826    pub checkpoint_mgr: CheckpointManager,
5827    pub lod_transitions: LodTransitionManager,
5828    pub analytics: StreamingAnalyticsSession,
5829    pub asset_catalogue: LevelAssetCatalogue,
5830    pub command_history: CommandHistory,
5831    pub distance_cache: StreamingDistanceCache,
5832    pub settings_panel: LevelStreamingSettingsPanel,
5833    pub pathfinder: Option<SectorWaypointPathfinder>,
5834    pub flow_state: WorldFlowState,
5835}
5836
5837#[derive(Debug, Default, Clone)]
5838pub struct WorldFlowState {
5839    pub is_loading_world: bool,
5840    pub load_progress: f32,
5841    pub current_phase: String,
5842    pub errors: Vec<String>,
5843    pub warnings: Vec<String>,
5844    pub is_simulation_mode: bool,
5845    pub last_checkpoint_id: Option<u64>,
5846}
5847
5848impl StreamingWorldManager {
5849    pub fn new(world_name: String) -> Self {
5850        let config = LevelStreamingEditorConfig::default();
5851        let settings = LevelStreamingSettingsPanel::new(&config);
5852        let editor = FullLevelStreamingEditor::new(config);
5853        Self {
5854            editor,
5855            dynamic_tracker: DynamicObjectTracker::new(DEFAULT_CELL_SIZE),
5856            composer: StreamingWorldComposer::new(world_name),
5857            roi_manager: RegionOfInterestManager::new(),
5858            checkpoint_mgr: CheckpointManager::new(),
5859            lod_transitions: LodTransitionManager::new(),
5860            analytics: StreamingAnalyticsSession::new(1, 0.0),
5861            asset_catalogue: LevelAssetCatalogue::new(),
5862            command_history: CommandHistory::new(128),
5863            distance_cache: StreamingDistanceCache::new(),
5864            settings_panel: settings,
5865            pathfinder: None,
5866            flow_state: WorldFlowState::default(),
5867        }
5868    }
5869
5870    pub fn tick(&mut self, dt_s: f32) {
5871        let cam_pos = self.editor.core.camera_position;
5872        let cam_dir = self.editor.core.camera_direction;
5873        let time_ms = self.editor.core.current_time_ms;
5874
5875        self.editor.tick(dt_s);
5876        self.roi_manager.update(cam_pos, time_ms);
5877        self.lod_transitions.update(dt_s);
5878
5879        let levels_vec: Vec<StreamingLevel> = self.editor.core.levels.values().cloned().collect();
5880        let memory_mb = self.editor.core.memory_manager.used_mb;
5881        self.analytics.update_peak_memory(memory_mb);
5882
5883        let _ = self.checkpoint_mgr.maybe_auto_checkpoint(cam_pos, cam_dir, &levels_vec, memory_mb, time_ms);
5884        self.distance_cache.update(cam_pos, &levels_vec, self.editor.core.current_frame);
5885
5886        if self.settings_panel.has_unsaved_changes() {
5887            self.settings_panel.apply_to_config(&mut self.editor.core.config);
5888            self.editor.core.set_memory_budget(self.editor.core.config.memory_budget_mb);
5889        }
5890
5891        let completed: Vec<(u64, LodLevel)> = self.lod_transitions.completed.drain(..).collect();
5892        for (level_id, new_lod) in completed {
5893            if let Some(level) = self.editor.core.levels.get_mut(&level_id) {
5894                level.current_lod = new_lod;
5895            }
5896        }
5897
5898        if self.pathfinder.is_none() && !self.editor.core.sector_graph.sectors.is_empty() {
5899            self.pathfinder = Some(SectorWaypointPathfinder::new(&self.editor.core.sector_graph));
5900        }
5901    }
5902
5903    pub fn do_command(&mut self, cmd: StreamingEditorCommand) {
5904        apply_streaming_command(&mut self.editor, &cmd);
5905        self.command_history.push(cmd);
5906    }
5907
5908    pub fn undo(&mut self) {
5909        if let Some(cmd) = self.command_history.undo() {
5910            undo_streaming_command(&mut self.editor, &cmd);
5911        }
5912    }
5913
5914    pub fn redo(&mut self) {
5915        if let Some(cmd) = self.command_history.redo() {
5916            apply_streaming_command(&mut self.editor, &cmd);
5917        }
5918    }
5919
5920    pub fn save_checkpoint(&mut self) -> Option<u64> {
5921        let cam_pos = self.editor.core.camera_position;
5922        let cam_dir = self.editor.core.camera_direction;
5923        let levels_vec: Vec<StreamingLevel> = self.editor.core.levels.values().cloned().collect();
5924        let memory_mb = self.editor.core.memory_manager.used_mb;
5925        let time_ms = self.editor.core.current_time_ms;
5926        let id = self.checkpoint_mgr.save("Manual".into(), cam_pos, cam_dir, &levels_vec, memory_mb, time_ms);
5927        self.flow_state.last_checkpoint_id = Some(id);
5928        Some(id)
5929    }
5930
5931    pub fn find_path_to_sector(&self, from_sector: u64, to_sector: u64) -> Option<Vec<u64>> {
5932        self.pathfinder.as_ref()?.find_sector_path(from_sector, to_sector)
5933    }
5934
5935    pub fn world_report(&self) -> WorldStreamingReport {
5936        let core_report = self.editor.core.get_streaming_report();
5937        WorldStreamingReport {
5938            core: core_report,
5939            dynamic_objects: self.dynamic_tracker.total_objects(),
5940            active_roi_count: self.roi_manager.active_regions.len(),
5941            lod_transitions_active: self.lod_transitions.active_count(),
5942            analytics_total_loads: self.analytics.total_loads,
5943            analytics_bandwidth_mb_s: self.analytics.average_bandwidth_mb_s(),
5944            asset_catalogue_mb: self.asset_catalogue.total_size_mb(),
5945            has_unsaved_settings: self.settings_panel.has_unsaved_changes(),
5946            can_undo: self.command_history.can_undo(),
5947            can_redo: self.command_history.can_redo(),
5948        }
5949    }
5950
5951    pub fn spawn_dynamic_object(&mut self, name: String, pos: Vec3, radius: f32) -> u64 {
5952        self.dynamic_tracker.spawn(name, pos, radius)
5953    }
5954
5955    pub fn update_dynamic_object(&mut self, id: u64, new_pos: Vec3, dt_s: f32) {
5956        let time_ms = self.editor.core.current_time_ms;
5957        self.dynamic_tracker.update_position(id, new_pos, dt_s, time_ms);
5958    }
5959
5960    pub fn get_levels_to_stream_for_object(&self, object_id: u64, extra_radius: f32) -> Vec<u64> {
5961        if let Some(obj) = self.dynamic_tracker.objects.get(&object_id) {
5962            return self.editor.core.query_levels_near(obj.position, obj.bounds_radius + extra_radius);
5963        }
5964        Vec::new()
5965    }
5966
5967    pub fn set_simulation_speed(&mut self, speed: f32) {
5968        self.editor.core.simulator.playback_speed = speed.max(0.0);
5969        self.flow_state.is_simulation_mode = speed > 0.0 && self.editor.core.simulator.is_running;
5970    }
5971
5972    pub fn full_reset(&mut self) {
5973        self.editor.core.reset_simulation();
5974        self.analytics = StreamingAnalyticsSession::new(self.analytics.session_id + 1, self.editor.core.current_time_ms);
5975        self.flow_state = WorldFlowState::default();
5976    }
5977}
5978
5979#[derive(Debug, Clone)]
5980pub struct WorldStreamingReport {
5981    pub core: StreamingReport,
5982    pub dynamic_objects: usize,
5983    pub active_roi_count: usize,
5984    pub lod_transitions_active: usize,
5985    pub analytics_total_loads: u32,
5986    pub analytics_bandwidth_mb_s: f32,
5987    pub asset_catalogue_mb: f32,
5988    pub has_unsaved_settings: bool,
5989    pub can_undo: bool,
5990    pub can_redo: bool,
5991}
5992
5993// ============================================================
5994// EXTENDED TESTS
5995// ============================================================
5996
5997#[cfg(test)]
5998mod extended_tests {
5999    use super::*;
6000
6001    #[test]
6002    fn test_dynamic_object_tracker() {
6003        let mut tracker = DynamicObjectTracker::new(512.0);
6004        let id = tracker.spawn("Npc1".into(), Vec3::ZERO, 1.0);
6005        assert!(tracker.objects.contains_key(&id));
6006        tracker.update_position(id, Vec3::new(600.0, 0.0, 0.0), 1.0, 1000.0);
6007        let obj = &tracker.objects[&id];
6008        assert_ne!(obj.current_cell, CellCoord::new(0, 0, 0));
6009    }
6010
6011    #[test]
6012    fn test_streaming_world_manager_tick() {
6013        let mut mgr = StreamingWorldManager::new("TestWorld".into());
6014        mgr.editor.core.update_camera(Vec3::new(100.0, 0.0, 100.0), Vec3::NEG_Z, Mat4::IDENTITY);
6015        mgr.tick(0.016);
6016        let report = mgr.world_report();
6017        assert_eq!(report.core.total_levels, 0);
6018    }
6019
6020    #[test]
6021    fn test_distance_cache() {
6022        let mut cache = StreamingDistanceCache::new();
6023        let asset = StreamingLevelAsset { id:1, name:"a".into(), file_path:"".into(),
6024            size_bytes:0, uncompressed_size_bytes:0, dependencies:vec![], load_time_estimate_ms:0.0 };
6025        let level = StreamingLevel::new(1,"a".into(),asset,Aabb::new(Vec3::new(100.,0.,0.),Vec3::new(200.,10.,10.)));
6026        cache.update(Vec3::ZERO, &[level], 1);
6027        assert!(cache.get(1).is_some());
6028        let dist = cache.get(1).unwrap();
6029        assert!((dist - 100.0).abs() < 1.0);
6030    }
6031
6032    #[test]
6033    fn test_lod_transition_smooth_step() {
6034        let mut t = LodTransition::new(1, LodLevel::Lod0, LodLevel::Lod1, 1.0);
6035        t.progress = 0.5;
6036        let alpha = t.blend_alpha();
6037        // smooth step at 0.5 = 0.5
6038        assert!((alpha - 0.5).abs() < 0.01);
6039        t.progress = 0.0;
6040        assert!((t.blend_alpha() - 0.0).abs() < 0.01);
6041        t.progress = 1.0;
6042        assert!((t.blend_alpha() - 1.0).abs() < 0.01);
6043    }
6044
6045    #[test]
6046    fn test_analytics_session() {
6047        let mut session = StreamingAnalyticsSession::new(1, 0.0);
6048        session.record_load(1, 250.0, 50.0);
6049        session.record_load(2, 150.0, 30.0);
6050        assert_eq!(session.total_loads, 2);
6051        assert!((session.average_load_latency_ms - 200.0).abs() < 1.0);
6052        assert_eq!(session.unique_levels_loaded.len(), 2);
6053    }
6054
6055    #[test]
6056    fn test_region_of_interest() {
6057        let mut mgr = RegionOfInterestManager::new();
6058        let bounds = Aabb::new(Vec3::ZERO, Vec3::splat(100.0));
6059        let id = mgr.add_region("Combat".into(), bounds, LoadPriority::High);
6060        mgr.update(Vec3::new(50.0, 0.0, 50.0), 0.0);
6061        assert!(mgr.active_regions.contains(&id));
6062        mgr.update(Vec3::new(500.0, 0.0, 500.0), 100.0);
6063        assert!(!mgr.active_regions.contains(&id));
6064    }
6065
6066    #[test]
6067    fn test_checkpoint_save_restore() {
6068        let mut mgr = CheckpointManager::new();
6069        let id = mgr.save("Test".into(), Vec3::ZERO, Vec3::NEG_Z, &[], 128.0, 1000.0);
6070        let cp = mgr.checkpoints.get(&id).unwrap();
6071        assert_eq!(cp.memory_used_mb, 128.0);
6072        assert_eq!(cp.loaded_level_ids.len(), 0);
6073    }
6074
6075    #[test]
6076    fn test_sector_pathfinding() {
6077        let mut sg = SectorGraph::new();
6078        sg.add_sector(Sector::new(1,"A".into(),Aabb::new(Vec3::ZERO,Vec3::splat(10.))));
6079        sg.add_sector(Sector::new(2,"B".into(),Aabb::new(Vec3::splat(10.),Vec3::splat(20.))));
6080        sg.add_sector(Sector::new(3,"C".into(),Aabb::new(Vec3::splat(20.),Vec3::splat(30.))));
6081        sg.add_portal(Portal::new(1,1,2,Vec3::new(10.,5.,5.),Vec3::X,Vec2::splat(2.)));
6082        sg.add_portal(Portal::new(2,2,3,Vec3::new(20.,5.,5.),Vec3::X,Vec2::splat(2.)));
6083        let pf = SectorWaypointPathfinder::new(&sg);
6084        let path = pf.find_sector_path(1,3).unwrap();
6085        assert_eq!(path, vec![1,2,3]);
6086    }
6087
6088    #[test]
6089    fn test_asset_catalogue() {
6090        let mut cat = LevelAssetCatalogue::new();
6091        let asset = StreamingLevelAsset { id:1, name:"Forest".into(), file_path:"".into(),
6092            size_bytes:1024*1024, uncompressed_size_bytes:0, dependencies:vec![], load_time_estimate_ms:200.0 };
6093        cat.register(asset);
6094        cat.tag_asset(1,"outdoor");
6095        assert_eq!(cat.assets_by_tag("outdoor").len(), 1);
6096        assert!((cat.total_size_mb() - 1.0).abs() < 0.01);
6097    }
6098
6099    #[test]
6100    fn test_cells_to_activate_velocity() {
6101        let coords = cells_to_activate(
6102            Vec3::ZERO, Vec3::new(20.0,0.0,0.0), 512.0, 256.0, 2.0, Vec3::ZERO
6103        );
6104        assert!(!coords.is_empty());
6105        // All coords should be unique (dedup)
6106        let mut sorted = coords.clone();
6107        sorted.sort();
6108        sorted.dedup();
6109        assert_eq!(sorted.len(), coords.len());
6110    }
6111
6112    #[test]
6113    fn test_streaming_flow_optimizer_batch_size() {
6114        let batch = StreamingFlowOptimizer::optimal_batch_size(100.0, 25.0, 500.0);
6115        assert!(batch >= 1 && batch <= MAX_CONCURRENT_LOADS);
6116    }
6117
6118    #[test]
6119    fn test_world_composer_layers() {
6120        let mut composer = StreamingWorldComposer::new("World".into());
6121        composer.add_to_layer("terrain", 1);
6122        composer.add_to_layer("terrain", 2);
6123        composer.add_to_layer("buildings", 3);
6124        assert_eq!(composer.levels_in_layer("terrain").len(), 2);
6125        assert_eq!(composer.all_managed_level_ids().len(), 3);
6126        assert_eq!(composer.count_layers(), 2);
6127    }
6128
6129    #[test]
6130    fn test_lod_analysis() {
6131        let asset = StreamingLevelAsset { id:1, name:"T".into(), file_path:"".into(),
6132            size_bytes:10*1024*1024, uncompressed_size_bytes:0, dependencies:vec![], load_time_estimate_ms:0.0 };
6133        let mut level = StreamingLevel::new(1,"T".into(),asset,Aabb::new(Vec3::ZERO,Vec3::splat(100.)));
6134        level.distance_to_camera = 50.0;
6135        level.current_lod = LodLevel::Lod0;
6136        let mgr = CombinedBudgetManager::new(1000.0);
6137        let analysis = analyze_lod_distribution(&[level.clone()], &mgr);
6138        assert_eq!(analysis.len(), 1);
6139        assert_eq!(analysis[0].current_lod, LodLevel::Lod0);
6140    }
6141
6142    #[test]
6143    fn test_streaming_importance() {
6144        let asset = StreamingLevelAsset { id:1, name:"T".into(), file_path:"".into(),
6145            size_bytes:0, uncompressed_size_bytes:0, dependencies:vec![], load_time_estimate_ms:0.0 };
6146        let level = StreamingLevel::new(1,"T".into(),asset,Aabb::new(Vec3::new(50.,0.,0.),Vec3::new(150.,50.,50.)));
6147        let importance = compute_streaming_importance(
6148            &level, Vec3::ZERO, Vec3::X, 5.0
6149        );
6150        assert!(importance.is_finite() && importance > 0.0);
6151    }
6152
6153    #[test]
6154    fn test_hysteresis() {
6155        assert!(hysteresis_check_load(80.0, 100.0, 10.0));
6156        assert!(!hysteresis_check_load(95.0, 100.0, 10.0));
6157        assert!(hysteresis_check_unload(125.0, 110.0, 10.0));
6158        assert!(!hysteresis_check_unload(115.0, 110.0, 10.0));
6159    }
6160}
6161
6162// ============================================================
6163// SECTION: Streaming Tile Map (2D overhead layout)
6164// ============================================================
6165
6166#[derive(Clone, Debug)]
6167pub struct StreamingTile {
6168    pub tile_x: i32,
6169    pub tile_y: i32,
6170    pub tile_size_world: f32,
6171    pub level_ids: Vec<u64>,
6172    pub terrain_height_min: f32,
6173    pub terrain_height_max: f32,
6174    pub is_water: bool,
6175    pub biome_id: u32,
6176    pub detail_density: f32,
6177    pub last_visited_time: f64,
6178}
6179
6180impl StreamingTile {
6181    pub fn new(tile_x: i32, tile_y: i32, tile_size: f32) -> Self {
6182        Self {
6183            tile_x,
6184            tile_y,
6185            tile_size_world: tile_size,
6186            level_ids: Vec::new(),
6187            terrain_height_min: 0.0,
6188            terrain_height_max: 100.0,
6189            is_water: false,
6190            biome_id: 0,
6191            detail_density: 1.0,
6192            last_visited_time: 0.0,
6193        }
6194    }
6195
6196    pub fn world_center(&self) -> Vec3 {
6197        Vec3::new(
6198            (self.tile_x as f32 + 0.5) * self.tile_size_world,
6199            (self.terrain_height_min + self.terrain_height_max) * 0.5,
6200            (self.tile_y as f32 + 0.5) * self.tile_size_world,
6201        )
6202    }
6203
6204    pub fn world_bounds(&self) -> Aabb {
6205        let min = Vec3::new(
6206            self.tile_x as f32 * self.tile_size_world,
6207            self.terrain_height_min,
6208            self.tile_y as f32 * self.tile_size_world,
6209        );
6210        let max = Vec3::new(
6211            (self.tile_x + 1) as f32 * self.tile_size_world,
6212            self.terrain_height_max,
6213            (self.tile_y + 1) as f32 * self.tile_size_world,
6214        );
6215        Aabb::new(min, max)
6216    }
6217
6218    pub fn distance_to_point(&self, point: Vec3) -> f32 {
6219        let center = self.world_center();
6220        let half = self.tile_size_world * 0.5;
6221        let dx = (center.x - point.x).abs() - half;
6222        let dz = (center.z - point.z).abs() - half;
6223        (dx.max(0.0) * dx.max(0.0) + dz.max(0.0) * dz.max(0.0)).sqrt()
6224    }
6225
6226    pub fn contains_point_2d(&self, x: f32, z: f32) -> bool {
6227        let min_x = self.tile_x as f32 * self.tile_size_world;
6228        let min_z = self.tile_y as f32 * self.tile_size_world;
6229        let max_x = min_x + self.tile_size_world;
6230        let max_z = min_z + self.tile_size_world;
6231        x >= min_x && x < max_x && z >= min_z && z < max_z
6232    }
6233}
6234
6235#[derive(Clone, Debug)]
6236pub struct StreamingTileMap {
6237    pub tile_size_world: f32,
6238    pub tiles: HashMap<(i32, i32), StreamingTile>,
6239    pub world_origin: Vec3,
6240    pub max_tiles: usize,
6241}
6242
6243impl StreamingTileMap {
6244    pub fn new(tile_size_world: f32) -> Self {
6245        Self {
6246            tile_size_world,
6247            tiles: HashMap::new(),
6248            world_origin: Vec3::ZERO,
6249            max_tiles: 1024,
6250        }
6251    }
6252
6253    pub fn world_to_tile(&self, world_pos: Vec3) -> (i32, i32) {
6254        let tx = ((world_pos.x - self.world_origin.x) / self.tile_size_world).floor() as i32;
6255        let tz = ((world_pos.z - self.world_origin.z) / self.tile_size_world).floor() as i32;
6256        (tx, tz)
6257    }
6258
6259    pub fn get_or_create(&mut self, tile_x: i32, tile_y: i32) -> &mut StreamingTile {
6260        self.tiles.entry((tile_x, tile_y)).or_insert_with(|| {
6261            StreamingTile::new(tile_x, tile_y, self.tile_size_world)
6262        })
6263    }
6264
6265    pub fn get(&self, tile_x: i32, tile_y: i32) -> Option<&StreamingTile> {
6266        self.tiles.get(&(tile_x, tile_y))
6267    }
6268
6269    pub fn tiles_in_radius(&self, center: Vec3, radius: f32) -> Vec<(i32, i32)> {
6270        let tile_radius = (radius / self.tile_size_world).ceil() as i32 + 1;
6271        let (cx, cz) = self.world_to_tile(center);
6272        let mut result = Vec::new();
6273        for tx in (cx - tile_radius)..=(cx + tile_radius) {
6274            for tz in (cz - tile_radius)..=(cz + tile_radius) {
6275                if let Some(tile) = self.tiles.get(&(tx, tz)) {
6276                    if tile.distance_to_point(center) <= radius {
6277                        result.push((tx, tz));
6278                    }
6279                } else {
6280                    // Check distance from center to potential tile
6281                    let half = self.tile_size_world * 0.5;
6282                    let tc_x = (tx as f32 + 0.5) * self.tile_size_world;
6283                    let tc_z = (tz as f32 + 0.5) * self.tile_size_world;
6284                    let dx = (center.x - tc_x).abs() - half;
6285                    let dz = (center.z - tc_z).abs() - half;
6286                    let dist = (dx.max(0.0).powi(2) + dz.max(0.0).powi(2)).sqrt();
6287                    if dist <= radius {
6288                        result.push((tx, tz));
6289                    }
6290                }
6291            }
6292        }
6293        result
6294    }
6295
6296    pub fn tiles_in_frustum(&self, frustum: &Frustum) -> Vec<(i32, i32)> {
6297        self.tiles.iter()
6298            .filter(|(_, tile)| frustum.test_aabb(&tile.world_bounds()))
6299            .map(|(&k, _)| k)
6300            .collect()
6301    }
6302
6303    pub fn add_level_to_tile(&mut self, tile_x: i32, tile_y: i32, level_id: u64) {
6304        let tile = self.get_or_create(tile_x, tile_y);
6305        if !tile.level_ids.contains(&level_id) {
6306            tile.level_ids.push(level_id);
6307        }
6308    }
6309
6310    pub fn remove_level_from_all(&mut self, level_id: u64) {
6311        for tile in self.tiles.values_mut() {
6312            tile.level_ids.retain(|&id| id != level_id);
6313        }
6314    }
6315
6316    pub fn tile_count(&self) -> usize {
6317        self.tiles.len()
6318    }
6319
6320    pub fn stale_tiles(&self, current_time: f64, max_age_seconds: f64) -> Vec<(i32, i32)> {
6321        self.tiles.iter()
6322            .filter(|(_, t)| current_time - t.last_visited_time > max_age_seconds)
6323            .map(|(&k, _)| k)
6324            .collect()
6325    }
6326
6327    pub fn evict_stale(&mut self, current_time: f64, max_age_seconds: f64) -> usize {
6328        let stale = self.stale_tiles(current_time, max_age_seconds);
6329        let count = stale.len();
6330        for key in stale {
6331            self.tiles.remove(&key);
6332        }
6333        count
6334    }
6335}
6336
6337// ============================================================
6338// SECTION: Level Instancer (manages multiple placed instances)
6339// ============================================================
6340
6341#[derive(Clone, Debug)]
6342pub struct LevelInstancer {
6343    pub instances: HashMap<u64, LevelInstance>,
6344    pub next_instance_id: u64,
6345    pub spatial_index: HashMap<(i32, i32), Vec<u64>>, // tile -> instance ids
6346    pub tile_size: f32,
6347}
6348
6349impl LevelInstancer {
6350    pub fn new(tile_size: f32) -> Self {
6351        Self {
6352            instances: HashMap::new(),
6353            next_instance_id: 1,
6354            spatial_index: HashMap::new(),
6355            tile_size,
6356        }
6357    }
6358
6359    pub fn place_instance(&mut self, level_id: u64, transform: Mat4) -> u64 {
6360        let id = self.next_instance_id;
6361        self.next_instance_id += 1;
6362        let instance = LevelInstance::new(id, level_id, transform);
6363        let tile_key = self.world_to_tile(instance.position());
6364        self.spatial_index.entry(tile_key).or_insert_with(Vec::new).push(id);
6365        self.instances.insert(id, instance);
6366        id
6367    }
6368
6369    fn world_to_tile(&self, pos: Vec3) -> (i32, i32) {
6370        ((pos.x / self.tile_size).floor() as i32,
6371         (pos.z / self.tile_size).floor() as i32)
6372    }
6373
6374    pub fn remove_instance(&mut self, id: u64) -> Option<LevelInstance> {
6375        if let Some(inst) = self.instances.remove(&id) {
6376            let tile_key = self.world_to_tile(inst.position());
6377            if let Some(list) = self.spatial_index.get_mut(&tile_key) {
6378                list.retain(|&i| i != id);
6379            }
6380            Some(inst)
6381        } else {
6382            None
6383        }
6384    }
6385
6386    pub fn instances_in_radius(&self, center: Vec3, radius: f32) -> Vec<u64> {
6387        let tile_r = (radius / self.tile_size).ceil() as i32 + 1;
6388        let (cx, cz) = self.world_to_tile(center);
6389        let mut result = Vec::new();
6390        for tx in (cx - tile_r)..=(cx + tile_r) {
6391            for tz in (cz - tile_r)..=(cz + tile_r) {
6392                if let Some(ids) = self.spatial_index.get(&(tx, tz)) {
6393                    for &id in ids {
6394                        if let Some(inst) = self.instances.get(&id) {
6395                            let dist = (inst.position() - center).length();
6396                            if dist <= radius {
6397                                result.push(id);
6398                            }
6399                        }
6400                    }
6401                }
6402            }
6403        }
6404        result
6405    }
6406
6407    pub fn instances_with_tag(&self, tag: &str) -> Vec<u64> {
6408        self.instances.values()
6409            .filter(|i| i.has_tag(tag))
6410            .map(|i| i.instance_id)
6411            .collect()
6412    }
6413
6414    pub fn instances_for_level(&self, level_id: u64) -> Vec<u64> {
6415        self.instances.values()
6416            .filter(|i| i.level_id == level_id)
6417            .map(|i| i.instance_id)
6418            .collect()
6419    }
6420
6421    pub fn update_transform(&mut self, id: u64, new_transform: Mat4) {
6422        let old_pos = self.instances.get(&id).map(|i| i.position());
6423        if let Some(inst) = self.instances.get_mut(&id) {
6424            inst.transform = new_transform;
6425            inst.last_modified_time += 0.001;
6426        }
6427        if let Some(old_pos) = old_pos {
6428            let old_tile = self.world_to_tile(old_pos);
6429            let new_pos = self.instances.get(&id).map(|i| i.position()).unwrap_or(old_pos);
6430            let new_tile = self.world_to_tile(new_pos);
6431            if old_tile != new_tile {
6432                if let Some(list) = self.spatial_index.get_mut(&old_tile) {
6433                    list.retain(|&i| i != id);
6434                }
6435                self.spatial_index.entry(new_tile).or_insert_with(Vec::new).push(id);
6436            }
6437        }
6438    }
6439
6440    pub fn count_by_level(&self) -> HashMap<u64, usize> {
6441        let mut counts: HashMap<u64, usize> = HashMap::new();
6442        for inst in self.instances.values() {
6443            *counts.entry(inst.level_id).or_insert(0) += 1;
6444        }
6445        counts
6446    }
6447
6448    pub fn visible_instances_in_frustum(&self, frustum: &Frustum, level_bounds: &HashMap<u64, Aabb>) -> Vec<u64> {
6449        self.instances.values()
6450            .filter(|inst| {
6451                if !inst.visible { return false; }
6452                if let Some(bounds) = level_bounds.get(&inst.level_id) {
6453                    // Transform bounds by instance transform (approximate AABB)
6454                    let pos = inst.position();
6455                    let translated = Aabb::new(bounds.min + pos, bounds.max + pos);
6456                    frustum.test_aabb(&translated)
6457                } else {
6458                    true
6459                }
6460            })
6461            .map(|i| i.instance_id)
6462            .collect()
6463    }
6464}
6465
6466// ============================================================
6467// SECTION: Terrain Patch LOD System
6468// ============================================================
6469
6470#[derive(Clone, Debug)]
6471pub struct TerrainPatch {
6472    pub patch_id: u32,
6473    pub grid_x: i32,
6474    pub grid_z: i32,
6475    pub patch_size: f32,
6476    pub current_lod: u32,
6477    pub max_lod: u32,
6478    pub height_data: Vec<f32>, // flattened height grid
6479    pub height_grid_res: u32,  // resolution per side
6480    pub vertex_count: u32,
6481    pub is_stitched: bool,
6482    pub neighbor_lods: [u32; 4], // N, S, E, W
6483    pub morph_fraction: f32,     // for smooth LOD transitions
6484}
6485
6486impl TerrainPatch {
6487    pub fn new(patch_id: u32, grid_x: i32, grid_z: i32, patch_size: f32, max_lod: u32) -> Self {
6488        let base_res = 64u32;
6489        let res = base_res;
6490        let height_data = vec![0.0f32; (res * res) as usize];
6491        Self {
6492            patch_id,
6493            grid_x,
6494            grid_z,
6495            patch_size,
6496            current_lod: 0,
6497            max_lod,
6498            height_data,
6499            height_grid_res: res,
6500            vertex_count: res * res,
6501            is_stitched: false,
6502            neighbor_lods: [0; 4],
6503            morph_fraction: 0.0,
6504        }
6505    }
6506
6507    pub fn world_position(&self) -> Vec3 {
6508        Vec3::new(
6509            self.grid_x as f32 * self.patch_size,
6510            0.0,
6511            self.grid_z as f32 * self.patch_size,
6512        )
6513    }
6514
6515    pub fn world_bounds(&self) -> Aabb {
6516        let min_x = self.grid_x as f32 * self.patch_size;
6517        let min_z = self.grid_z as f32 * self.patch_size;
6518        let h_min = self.height_data.iter().copied().fold(f32::INFINITY, f32::min);
6519        let h_max = self.height_data.iter().copied().fold(f32::NEG_INFINITY, f32::max);
6520        Aabb::new(
6521            Vec3::new(min_x, h_min, min_z),
6522            Vec3::new(min_x + self.patch_size, h_max, min_z + self.patch_size),
6523        )
6524    }
6525
6526    pub fn sample_height_bilinear(&self, local_x: f32, local_z: f32) -> f32 {
6527        // Sample height using bilinear interpolation
6528        let res = self.height_grid_res as f32;
6529        let u = (local_x / self.patch_size) * (res - 1.0);
6530        let v = (local_z / self.patch_size) * (res - 1.0);
6531        let x0 = u.floor() as usize;
6532        let z0 = v.floor() as usize;
6533        let x1 = (x0 + 1).min(self.height_grid_res as usize - 1);
6534        let z1 = (z0 + 1).min(self.height_grid_res as usize - 1);
6535        let fx = u - u.floor();
6536        let fz = v - v.floor();
6537        let res_u = self.height_grid_res as usize;
6538        let h00 = self.height_data[z0 * res_u + x0];
6539        let h10 = self.height_data[z0 * res_u + x1];
6540        let h01 = self.height_data[z1 * res_u + x0];
6541        let h11 = self.height_data[z1 * res_u + x1];
6542        h00 * (1.0 - fx) * (1.0 - fz)
6543            + h10 * fx * (1.0 - fz)
6544            + h01 * (1.0 - fx) * fz
6545            + h11 * fx * fz
6546    }
6547
6548    pub fn compute_normal_at(&self, local_x: f32, local_z: f32) -> Vec3 {
6549        let step = self.patch_size / self.height_grid_res as f32;
6550        let hx_plus  = self.sample_height_bilinear((local_x + step).min(self.patch_size), local_z);
6551        let hx_minus = self.sample_height_bilinear((local_x - step).max(0.0), local_z);
6552        let hz_plus  = self.sample_height_bilinear(local_x, (local_z + step).min(self.patch_size));
6553        let hz_minus = self.sample_height_bilinear(local_x, (local_z - step).max(0.0));
6554        let grad_x = (hx_plus - hx_minus) / (2.0 * step);
6555        let grad_z = (hz_plus - hz_minus) / (2.0 * step);
6556        Vec3::new(-grad_x, 1.0, -grad_z).normalize()
6557    }
6558
6559    pub fn desired_lod_for_distance(&self, distance: f32) -> u32 {
6560        let thresholds = [50.0, 150.0, 400.0, 900.0, 2000.0];
6561        for (lod, &threshold) in thresholds.iter().enumerate() {
6562            if distance < threshold {
6563                return lod as u32;
6564            }
6565        }
6566        self.max_lod
6567    }
6568
6569    pub fn update_lod(&mut self, camera_pos: Vec3) {
6570        let center = self.world_position() + Vec3::splat(self.patch_size * 0.5);
6571        let dist = (camera_pos - center).length();
6572        let desired = self.desired_lod_for_distance(dist);
6573        if desired != self.current_lod {
6574            self.morph_fraction = 0.0;
6575        } else {
6576            self.morph_fraction = (self.morph_fraction + 0.05).min(1.0);
6577        }
6578        self.current_lod = desired.min(self.max_lod);
6579        // Recompute vertex count for this LOD
6580        let step = 1u32 << self.current_lod;
6581        let reduced_res = (self.height_grid_res / step).max(2);
6582        self.vertex_count = reduced_res * reduced_res;
6583    }
6584
6585    pub fn needs_stitching(&self) -> bool {
6586        self.neighbor_lods.iter().any(|&n| n != self.current_lod)
6587    }
6588
6589    pub fn stitch_skirt_vertices(&self) -> Vec<Vec3> {
6590        // Generate skirt vertices around the patch edge to hide T-junctions
6591        let mut skirt = Vec::new();
6592        let step = 1u32 << self.current_lod;
6593        let res = self.height_grid_res / step;
6594        let cell_size = self.patch_size / res as f32;
6595        let base = self.world_position();
6596        // Bottom edge (z=0)
6597        for i in 0..=res {
6598            let x = base.x + i as f32 * cell_size;
6599            let h = self.sample_height_bilinear(i as f32 * cell_size, 0.0);
6600            skirt.push(Vec3::new(x, h, base.z));
6601            skirt.push(Vec3::new(x, h - 1.0, base.z)); // skirt hanging down
6602        }
6603        skirt
6604    }
6605}
6606
6607// ============================================================
6608// SECTION: Terrain Manager
6609// ============================================================
6610
6611#[derive(Clone, Debug)]
6612pub struct TerrainManager {
6613    pub patches: HashMap<(i32, i32), TerrainPatch>,
6614    pub patch_size: f32,
6615    pub max_lod: u32,
6616    pub streaming_radius: f32,
6617    pub total_vertices_rendered: u32,
6618    pub total_patches_visible: u32,
6619}
6620
6621impl TerrainManager {
6622    pub fn new(patch_size: f32, max_lod: u32, streaming_radius: f32) -> Self {
6623        Self {
6624            patches: HashMap::new(),
6625            patch_size,
6626            max_lod,
6627            streaming_radius,
6628            total_vertices_rendered: 0,
6629            total_patches_visible: 0,
6630        }
6631    }
6632
6633    pub fn get_or_create_patch(&mut self, grid_x: i32, grid_z: i32) -> &mut TerrainPatch {
6634        let sz = self.patch_size;
6635        let ml = self.max_lod;
6636        let next_id = self.patches.len() as u32 + 1;
6637        self.patches.entry((grid_x, grid_z)).or_insert_with(|| {
6638            TerrainPatch::new(next_id, grid_x, grid_z, sz, ml)
6639        })
6640    }
6641
6642    pub fn update(&mut self, camera_pos: Vec3) {
6643        let tile_r = (self.streaming_radius / self.patch_size).ceil() as i32 + 1;
6644        let cx = (camera_pos.x / self.patch_size).floor() as i32;
6645        let cz = (camera_pos.z / self.patch_size).floor() as i32;
6646        let mut total_verts = 0u32;
6647        let mut visible = 0u32;
6648        for tx in (cx - tile_r)..=(cx + tile_r) {
6649            for tz in (cz - tile_r)..=(cz + tile_r) {
6650                let center = Vec3::new(
6651                    (tx as f32 + 0.5) * self.patch_size,
6652                    camera_pos.y,
6653                    (tz as f32 + 0.5) * self.patch_size,
6654                );
6655                let dist = (camera_pos - center).length();
6656                if dist <= self.streaming_radius {
6657                    let patch = self.get_or_create_patch(tx, tz);
6658                    patch.update_lod(camera_pos);
6659                    total_verts += patch.vertex_count;
6660                    visible += 1;
6661                }
6662            }
6663        }
6664        // Update neighbor LODs for stitching
6665        let keys: Vec<(i32, i32)> = self.patches.keys().copied().collect();
6666        for &(tx, tz) in &keys {
6667            let neighbors = [
6668                ((tx, tz - 1), 0usize),
6669                ((tx, tz + 1), 1),
6670                ((tx + 1, tz), 2),
6671                ((tx - 1, tz), 3),
6672            ];
6673            let my_lod = self.patches[&(tx, tz)].current_lod;
6674            let _ = my_lod;
6675            let mut nlods = [0u32; 4];
6676            for (nkey, dir) in &neighbors {
6677                nlods[*dir] = self.patches.get(nkey).map(|p| p.current_lod).unwrap_or(0);
6678            }
6679            if let Some(patch) = self.patches.get_mut(&(tx, tz)) {
6680                patch.neighbor_lods = nlods;
6681            }
6682        }
6683        self.total_vertices_rendered = total_verts;
6684        self.total_patches_visible = visible;
6685    }
6686
6687    pub fn sample_height_world(&self, world_x: f32, world_z: f32) -> f32 {
6688        let gx = (world_x / self.patch_size).floor() as i32;
6689        let gz = (world_z / self.patch_size).floor() as i32;
6690        if let Some(patch) = self.patches.get(&(gx, gz)) {
6691            let local_x = world_x - gx as f32 * self.patch_size;
6692            let local_z = world_z - gz as f32 * self.patch_size;
6693            patch.sample_height_bilinear(local_x.max(0.0), local_z.max(0.0))
6694        } else {
6695            0.0
6696        }
6697    }
6698
6699    pub fn visible_patch_count(&self) -> usize {
6700        self.patches.len()
6701    }
6702
6703    pub fn patches_needing_stitch(&self) -> Vec<(i32, i32)> {
6704        self.patches.iter()
6705            .filter(|(_, p)| p.needs_stitching())
6706            .map(|(&k, _)| k)
6707            .collect()
6708    }
6709}
6710
6711// ============================================================
6712// SECTION: Streaming Priority Queue with Deadline Scheduling
6713// ============================================================
6714
6715#[derive(Clone, Debug)]
6716pub struct DeadlineRequest {
6717    pub id: u64,
6718    pub priority: f32,
6719    pub deadline_seconds: f64,
6720    pub estimated_load_ms: f32,
6721    pub size_bytes: u64,
6722    pub level_id: u64,
6723    pub request_time: f64,
6724    pub cancelled: bool,
6725}
6726
6727impl DeadlineRequest {
6728    pub fn urgency_at(&self, current_time: f64) -> f32 {
6729        let remaining = (self.deadline_seconds - current_time).max(0.001) as f32;
6730        let normalized_load = self.estimated_load_ms / 1000.0;
6731        self.priority * (normalized_load / remaining).min(100.0)
6732    }
6733
6734    pub fn is_overdue(&self, current_time: f64) -> bool {
6735        current_time > self.deadline_seconds
6736    }
6737
6738    pub fn slack_ms(&self, current_time: f64) -> f32 {
6739        ((self.deadline_seconds - current_time) * 1000.0 - self.estimated_load_ms as f64).max(0.0) as f32
6740    }
6741}
6742
6743#[derive(Clone, Debug)]
6744pub struct DeadlineScheduler {
6745    pub requests: Vec<DeadlineRequest>,
6746    pub next_request_id: u64,
6747    pub total_scheduled: u64,
6748    pub total_completed: u64,
6749    pub total_missed: u64,
6750    pub bandwidth_bytes_per_sec: f64,
6751    pub inflight_bytes: u64,
6752    pub max_inflight_bytes: u64,
6753}
6754
6755impl DeadlineScheduler {
6756    pub fn new(bandwidth_bytes_per_sec: f64) -> Self {
6757        Self {
6758            requests: Vec::new(),
6759            next_request_id: 1,
6760            total_scheduled: 0,
6761            total_completed: 0,
6762            total_missed: 0,
6763            bandwidth_bytes_per_sec,
6764            inflight_bytes: 0,
6765            max_inflight_bytes: 64 * 1024 * 1024, // 64 MB in flight
6766        }
6767    }
6768
6769    pub fn submit(&mut self, level_id: u64, priority: f32, deadline: f64, size_bytes: u64,
6770                  estimated_load_ms: f32, current_time: f64) -> u64 {
6771        let id = self.next_request_id;
6772        self.next_request_id += 1;
6773        self.requests.push(DeadlineRequest {
6774            id,
6775            priority,
6776            deadline_seconds: deadline,
6777            estimated_load_ms,
6778            size_bytes,
6779            level_id,
6780            request_time: current_time,
6781            cancelled: false,
6782        });
6783        self.total_scheduled += 1;
6784        id
6785    }
6786
6787    pub fn cancel(&mut self, request_id: u64) {
6788        if let Some(r) = self.requests.iter_mut().find(|r| r.id == request_id) {
6789            r.cancelled = true;
6790        }
6791    }
6792
6793    pub fn update(&mut self, current_time: f64, delta_seconds: f64) {
6794        // Remove cancelled requests
6795        self.requests.retain(|r| !r.cancelled);
6796        // Check for missed deadlines
6797        let missed: Vec<u64> = self.requests.iter()
6798            .filter(|r| r.is_overdue(current_time))
6799            .map(|r| r.id)
6800            .collect();
6801        self.total_missed += missed.len() as u64;
6802        self.requests.retain(|r| !r.is_overdue(current_time));
6803        // Compute available bandwidth this tick
6804        let available_bytes = (self.bandwidth_bytes_per_sec * delta_seconds) as u64;
6805        self.inflight_bytes = self.inflight_bytes.saturating_sub(available_bytes);
6806    }
6807
6808    pub fn next_batch(&mut self, current_time: f64, max_count: usize) -> Vec<u64> {
6809        // Sort by Earliest Deadline First (EDF) with priority tie-breaking
6810        let mut sortable: Vec<(usize, f32)> = self.requests.iter().enumerate()
6811            .map(|(i, r)| (i, r.urgency_at(current_time)))
6812            .collect();
6813        sortable.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
6814        let mut result = Vec::new();
6815        let mut inflight = self.inflight_bytes;
6816        for (idx, _urgency) in sortable.iter().take(max_count) {
6817            let r = &self.requests[*idx];
6818            if inflight + r.size_bytes <= self.max_inflight_bytes {
6819                inflight += r.size_bytes;
6820                result.push(r.id);
6821            }
6822        }
6823        self.inflight_bytes = inflight;
6824        result
6825    }
6826
6827    pub fn complete_request(&mut self, request_id: u64) {
6828        if let Some(pos) = self.requests.iter().position(|r| r.id == request_id) {
6829            let r = self.requests.remove(pos);
6830            self.inflight_bytes = self.inflight_bytes.saturating_sub(r.size_bytes);
6831            self.total_completed += 1;
6832        }
6833    }
6834
6835    pub fn utilization(&self) -> f32 {
6836        self.inflight_bytes as f32 / self.max_inflight_bytes as f32
6837    }
6838
6839    pub fn deadline_miss_rate(&self) -> f32 {
6840        if self.total_scheduled == 0 { return 0.0; }
6841        self.total_missed as f32 / self.total_scheduled as f32
6842    }
6843}
6844
6845// ============================================================
6846// SECTION: Spatial Hash Acceleration
6847// ============================================================
6848
6849#[derive(Clone, Debug)]
6850pub struct SpatialHash3D {
6851    pub cell_size: f32,
6852    cells: HashMap<(i32, i32, i32), Vec<u64>>,
6853    pub object_cells: HashMap<u64, (i32, i32, i32)>,
6854}
6855
6856impl SpatialHash3D {
6857    pub fn new(cell_size: f32) -> Self {
6858        Self {
6859            cell_size,
6860            cells: HashMap::new(),
6861            object_cells: HashMap::new(),
6862        }
6863    }
6864
6865    fn hash_pos(&self, pos: Vec3) -> (i32, i32, i32) {
6866        (
6867            (pos.x / self.cell_size).floor() as i32,
6868            (pos.y / self.cell_size).floor() as i32,
6869            (pos.z / self.cell_size).floor() as i32,
6870        )
6871    }
6872
6873    pub fn insert(&mut self, object_id: u64, pos: Vec3) {
6874        let key = self.hash_pos(pos);
6875        self.cells.entry(key).or_insert_with(Vec::new).push(object_id);
6876        self.object_cells.insert(object_id, key);
6877    }
6878
6879    pub fn remove(&mut self, object_id: u64) {
6880        if let Some(&key) = self.object_cells.get(&object_id) {
6881            if let Some(list) = self.cells.get_mut(&key) {
6882                list.retain(|&id| id != object_id);
6883            }
6884            self.object_cells.remove(&object_id);
6885        }
6886    }
6887
6888    pub fn update(&mut self, object_id: u64, new_pos: Vec3) {
6889        self.remove(object_id);
6890        self.insert(object_id, new_pos);
6891    }
6892
6893    pub fn query_radius(&self, center: Vec3, radius: f32) -> Vec<u64> {
6894        let r_cells = (radius / self.cell_size).ceil() as i32 + 1;
6895        let cc = self.hash_pos(center);
6896        let mut result = Vec::new();
6897        for x in (cc.0 - r_cells)..=(cc.0 + r_cells) {
6898            for y in (cc.1 - r_cells)..=(cc.1 + r_cells) {
6899                for z in (cc.2 - r_cells)..=(cc.2 + r_cells) {
6900                    if let Some(list) = self.cells.get(&(x, y, z)) {
6901                        result.extend_from_slice(list);
6902                    }
6903                }
6904            }
6905        }
6906        result
6907    }
6908
6909    pub fn query_aabb(&self, aabb: &Aabb) -> Vec<u64> {
6910        let min_key = self.hash_pos(aabb.min);
6911        let max_key = self.hash_pos(aabb.max);
6912        let mut result = Vec::new();
6913        for x in min_key.0..=max_key.0 {
6914            for y in min_key.1..=max_key.1 {
6915                for z in min_key.2..=max_key.2 {
6916                    if let Some(list) = self.cells.get(&(x, y, z)) {
6917                        result.extend_from_slice(list);
6918                    }
6919                }
6920            }
6921        }
6922        result
6923    }
6924
6925    pub fn object_count(&self) -> usize {
6926        self.object_cells.len()
6927    }
6928
6929    pub fn cell_count(&self) -> usize {
6930        self.cells.len()
6931    }
6932
6933    pub fn average_occupancy(&self) -> f32 {
6934        if self.cells.is_empty() { return 0.0; }
6935        let total: usize = self.cells.values().map(|v| v.len()).sum();
6936        total as f32 / self.cells.len() as f32
6937    }
6938}
6939
6940// ============================================================
6941// SECTION: Volumetric Fog Streaming Zone
6942// ============================================================
6943
6944#[derive(Clone, Debug)]
6945pub struct FogZone {
6946    pub zone_id: u32,
6947    pub bounds: Aabb,
6948    pub fog_density: f32,
6949    pub fog_color: Vec3,
6950    pub scatter_coefficient: f32,
6951    pub absorption_coefficient: f32,
6952    pub height_falloff: f32,    // exponential height fog falloff
6953    pub height_offset: f32,
6954    pub animation_speed: f32,
6955    pub turbulence: f32,
6956    pub enabled: bool,
6957    pub blend_distance: f32,    // transition zone width
6958}
6959
6960impl FogZone {
6961    pub fn new(zone_id: u32, bounds: Aabb) -> Self {
6962        Self {
6963            zone_id,
6964            bounds,
6965            fog_density: 0.01,
6966            fog_color: Vec3::new(0.7, 0.75, 0.8),
6967            scatter_coefficient: 0.005,
6968            absorption_coefficient: 0.003,
6969            height_falloff: 0.01,
6970            height_offset: 0.0,
6971            animation_speed: 0.1,
6972            turbulence: 0.5,
6973            enabled: true,
6974            blend_distance: 50.0,
6975        }
6976    }
6977
6978    pub fn density_at(&self, world_pos: Vec3, time: f64) -> f32 {
6979        if !self.enabled { return 0.0; }
6980        if !self.bounds.contains_point(world_pos) { return 0.0; }
6981        // Height-based exponential falloff
6982        let h = (world_pos.y - self.height_offset).max(0.0);
6983        let height_factor = (-self.height_falloff * h).exp();
6984        // Turbulence using sine approximation
6985        let t = time as f32;
6986        let noise = (world_pos.x * 0.1 + t * self.animation_speed).sin()
6987            * (world_pos.z * 0.1 + t * self.animation_speed * 0.7).cos()
6988            * self.turbulence * 0.5 + 0.5;
6989        let blend = self.blend_factor(world_pos);
6990        self.fog_density * height_factor * (1.0 + noise) * blend
6991    }
6992
6993    fn blend_factor(&self, pos: Vec3) -> f32 {
6994        // Smooth blend at zone boundaries
6995        let min_dist = [
6996            pos.x - self.bounds.min.x,
6997            self.bounds.max.x - pos.x,
6998            pos.y - self.bounds.min.y,
6999            self.bounds.max.y - pos.y,
7000            pos.z - self.bounds.min.z,
7001            self.bounds.max.z - pos.z,
7002        ].iter().copied().fold(f32::INFINITY, f32::min);
7003        let t = (min_dist / self.blend_distance).clamp(0.0, 1.0);
7004        t * t * (3.0 - 2.0 * t)
7005    }
7006
7007    pub fn transmittance_along_ray(&self, start: Vec3, end: Vec3, samples: u32, time: f64) -> f32 {
7008        // Beer-Lambert law: T = exp(-integral(sigma_t * ds))
7009        let mut optical_depth = 0.0f32;
7010        let dir = end - start;
7011        let total_len = dir.length();
7012        if total_len < 1e-6 { return 1.0; }
7013        let step = total_len / samples as f32;
7014        let d = dir / total_len;
7015        for i in 0..samples {
7016            let t = (i as f32 + 0.5) * step;
7017            let pos = start + d * t;
7018            let density = self.density_at(pos, time);
7019            optical_depth += density * step * (self.scatter_coefficient + self.absorption_coefficient);
7020        }
7021        (-optical_depth).exp()
7022    }
7023
7024    pub fn phase_function_henyey_greenstein(cos_theta: f32, g: f32) -> f32 {
7025        // Henyey-Greenstein phase function for anisotropic scattering
7026        let g2 = g * g;
7027        let denom = (1.0 + g2 - 2.0 * g * cos_theta).powf(1.5);
7028        (1.0 - g2) / (4.0 * std::f32::consts::PI * denom.max(1e-10))
7029    }
7030}
7031
7032// ============================================================
7033// SECTION: Level Streaming Scene Graph Node
7034// ============================================================
7035
7036#[derive(Clone, Debug)]
7037pub struct SceneNode {
7038    pub node_id: u64,
7039    pub name: String,
7040    pub local_transform: Mat4,
7041    pub world_transform: Mat4,
7042    pub parent_id: Option<u64>,
7043    pub children: Vec<u64>,
7044    pub level_id: Option<u64>,
7045    pub is_static: bool,
7046    pub dirty: bool,
7047    pub visibility_distance: f32,
7048    pub lod_bias: f32,
7049}
7050
7051impl SceneNode {
7052    pub fn new(node_id: u64, name: &str) -> Self {
7053        Self {
7054            node_id,
7055            name: name.to_string(),
7056            local_transform: Mat4::IDENTITY,
7057            world_transform: Mat4::IDENTITY,
7058            parent_id: None,
7059            children: Vec::new(),
7060            level_id: None,
7061            is_static: false,
7062            dirty: true,
7063            visibility_distance: 1000.0,
7064            lod_bias: 0.0,
7065        }
7066    }
7067
7068    pub fn set_local_transform(&mut self, transform: Mat4) {
7069        self.local_transform = transform;
7070        self.dirty = true;
7071    }
7072
7073    pub fn world_position(&self) -> Vec3 {
7074        Vec3::new(
7075            self.world_transform.w_axis.x,
7076            self.world_transform.w_axis.y,
7077            self.world_transform.w_axis.z,
7078        )
7079    }
7080
7081    pub fn is_visible_from(&self, camera_pos: Vec3) -> bool {
7082        let dist = (self.world_position() - camera_pos).length();
7083        dist <= self.visibility_distance
7084    }
7085}
7086
7087#[derive(Clone, Debug)]
7088pub struct SceneGraph {
7089    pub nodes: HashMap<u64, SceneNode>,
7090    pub root_nodes: Vec<u64>,
7091    pub next_node_id: u64,
7092}
7093
7094impl SceneGraph {
7095    pub fn new() -> Self {
7096        Self {
7097            nodes: HashMap::new(),
7098            root_nodes: Vec::new(),
7099            next_node_id: 1,
7100        }
7101    }
7102
7103    pub fn create_node(&mut self, name: &str) -> u64 {
7104        let id = self.next_node_id;
7105        self.next_node_id += 1;
7106        self.nodes.insert(id, SceneNode::new(id, name));
7107        self.root_nodes.push(id);
7108        id
7109    }
7110
7111    pub fn attach_child(&mut self, parent_id: u64, child_id: u64) {
7112        if let Some(parent) = self.nodes.get_mut(&parent_id) {
7113            if !parent.children.contains(&child_id) {
7114                parent.children.push(child_id);
7115            }
7116        }
7117        if let Some(child) = self.nodes.get_mut(&child_id) {
7118            child.parent_id = Some(parent_id);
7119            child.dirty = true;
7120        }
7121        self.root_nodes.retain(|&id| id != child_id);
7122    }
7123
7124    pub fn detach_child(&mut self, child_id: u64) {
7125        let parent_id = self.nodes.get(&child_id).and_then(|n| n.parent_id);
7126        if let Some(pid) = parent_id {
7127            if let Some(parent) = self.nodes.get_mut(&pid) {
7128                parent.children.retain(|&id| id != child_id);
7129            }
7130        }
7131        if let Some(node) = self.nodes.get_mut(&child_id) {
7132            node.parent_id = None;
7133            node.dirty = true;
7134        }
7135        if !self.root_nodes.contains(&child_id) {
7136            self.root_nodes.push(child_id);
7137        }
7138    }
7139
7140    pub fn update_world_transforms(&mut self) {
7141        // Iterative BFS to propagate dirty transforms
7142        let roots = self.root_nodes.clone();
7143        let mut queue = std::collections::VecDeque::new();
7144        for root in roots {
7145            if let Some(node) = self.nodes.get_mut(&root) {
7146                if node.dirty {
7147                    node.world_transform = node.local_transform;
7148                    node.dirty = false;
7149                }
7150            }
7151            queue.push_back(root);
7152        }
7153        while let Some(nid) = queue.pop_front() {
7154            let (parent_world, children) = if let Some(node) = self.nodes.get(&nid) {
7155                (node.world_transform, node.children.clone())
7156            } else {
7157                continue;
7158            };
7159            for child_id in children {
7160                if let Some(child) = self.nodes.get_mut(&child_id) {
7161                    if child.dirty {
7162                        child.world_transform = parent_world * child.local_transform;
7163                        child.dirty = false;
7164                    }
7165                }
7166                queue.push_back(child_id);
7167            }
7168        }
7169    }
7170
7171    pub fn find_by_name(&self, name: &str) -> Option<u64> {
7172        self.nodes.values()
7173            .find(|n| n.name == name)
7174            .map(|n| n.node_id)
7175    }
7176
7177    pub fn collect_subtree(&self, root_id: u64) -> Vec<u64> {
7178        let mut result = Vec::new();
7179        let mut queue = std::collections::VecDeque::new();
7180        queue.push_back(root_id);
7181        while let Some(nid) = queue.pop_front() {
7182            result.push(nid);
7183            if let Some(node) = self.nodes.get(&nid) {
7184                for &child in &node.children {
7185                    queue.push_back(child);
7186                }
7187            }
7188        }
7189        result
7190    }
7191
7192    pub fn depth_of(&self, node_id: u64) -> u32 {
7193        let mut depth = 0u32;
7194        let mut current = node_id;
7195        loop {
7196            if let Some(node) = self.nodes.get(&current) {
7197                if let Some(pid) = node.parent_id {
7198                    depth += 1;
7199                    current = pid;
7200                } else {
7201                    break;
7202                }
7203            } else {
7204                break;
7205            }
7206        }
7207        depth
7208    }
7209
7210    pub fn node_count(&self) -> usize {
7211        self.nodes.len()
7212    }
7213
7214    pub fn root_count(&self) -> usize {
7215        self.root_nodes.len()
7216    }
7217
7218    pub fn remove_subtree(&mut self, root_id: u64) -> usize {
7219        let subtree = self.collect_subtree(root_id);
7220        let count = subtree.len();
7221        self.detach_child(root_id);
7222        for id in &subtree {
7223            self.nodes.remove(id);
7224        }
7225        self.root_nodes.retain(|id| !subtree.contains(id));
7226        count
7227    }
7228}
7229
7230// ============================================================
7231// SECTION: Streaming World Metrics Dashboard
7232// ============================================================
7233
7234#[derive(Clone, Debug, Default)]
7235pub struct StreamingMetricsDashboard {
7236    pub frame_number: u64,
7237    pub current_time: f64,
7238    // Load/unload counts
7239    pub loads_this_frame: u32,
7240    pub unloads_this_frame: u32,
7241    pub total_loads: u64,
7242    pub total_unloads: u64,
7243    // Memory
7244    pub peak_memory_mb: f32,
7245    pub current_memory_mb: f32,
7246    pub memory_budget_mb: f32,
7247    // Performance
7248    pub avg_load_time_ms: f32,
7249    pub max_load_time_ms: f32,
7250    pub streaming_stalls: u32,
7251    pub frames_with_load: u32,
7252    // Counts
7253    pub active_levels: u32,
7254    pub loading_levels: u32,
7255    pub visible_levels: u32,
7256    pub culled_levels: u32,
7257    // Bandwidth
7258    pub bytes_loaded_this_sec: u64,
7259    pub bytes_unloaded_this_sec: u64,
7260    pub bandwidth_utilization: f32,
7261    // History ring buffer
7262    pub memory_history: VecDeque<f32>,
7263    pub fps_history: VecDeque<f32>,
7264    history_capacity: usize,
7265}
7266
7267impl StreamingMetricsDashboard {
7268    pub fn new(history_capacity: usize) -> Self {
7269        Self {
7270            memory_history: VecDeque::with_capacity(history_capacity),
7271            fps_history: VecDeque::with_capacity(history_capacity),
7272            history_capacity,
7273            memory_budget_mb: 512.0,
7274            ..Default::default()
7275        }
7276    }
7277
7278    pub fn begin_frame(&mut self, time: f64, fps: f32) {
7279        self.frame_number += 1;
7280        self.current_time = time;
7281        self.loads_this_frame = 0;
7282        self.unloads_this_frame = 0;
7283        // Update history
7284        if self.memory_history.len() >= self.history_capacity {
7285            self.memory_history.pop_front();
7286        }
7287        self.memory_history.push_back(self.current_memory_mb);
7288        if self.fps_history.len() >= self.history_capacity {
7289            self.fps_history.pop_front();
7290        }
7291        self.fps_history.push_back(fps);
7292    }
7293
7294    pub fn record_load(&mut self, load_time_ms: f32, bytes: u64) {
7295        self.loads_this_frame += 1;
7296        self.total_loads += 1;
7297        self.bytes_loaded_this_sec += bytes;
7298        if load_time_ms > self.max_load_time_ms {
7299            self.max_load_time_ms = load_time_ms;
7300        }
7301        // Rolling average
7302        let n = self.total_loads as f32;
7303        self.avg_load_time_ms = (self.avg_load_time_ms * (n - 1.0) + load_time_ms) / n;
7304    }
7305
7306    pub fn record_unload(&mut self, bytes: u64) {
7307        self.unloads_this_frame += 1;
7308        self.total_unloads += 1;
7309        self.bytes_unloaded_this_sec += bytes;
7310    }
7311
7312    pub fn record_stall(&mut self) {
7313        self.streaming_stalls += 1;
7314    }
7315
7316    pub fn update_memory(&mut self, used_mb: f32) {
7317        self.current_memory_mb = used_mb;
7318        if used_mb > self.peak_memory_mb {
7319            self.peak_memory_mb = used_mb;
7320        }
7321    }
7322
7323    pub fn memory_utilization(&self) -> f32 {
7324        if self.memory_budget_mb < 1.0 { return 0.0; }
7325        self.current_memory_mb / self.memory_budget_mb
7326    }
7327
7328    pub fn average_fps(&self) -> f32 {
7329        if self.fps_history.is_empty() { return 0.0; }
7330        self.fps_history.iter().sum::<f32>() / self.fps_history.len() as f32
7331    }
7332
7333    pub fn min_fps(&self) -> f32 {
7334        self.fps_history.iter().copied().fold(f32::INFINITY, f32::min)
7335    }
7336
7337    pub fn memory_trend(&self) -> f32 {
7338        // Slope of memory over last N frames (linear regression simplified)
7339        let n = self.memory_history.len();
7340        if n < 2 { return 0.0; }
7341        let x_mean = (n as f32 - 1.0) * 0.5;
7342        let y_mean: f32 = self.memory_history.iter().sum::<f32>() / n as f32;
7343        let mut numer = 0.0f32;
7344        let mut denom = 0.0f32;
7345        for (i, &y) in self.memory_history.iter().enumerate() {
7346            let x = i as f32 - x_mean;
7347            numer += x * (y - y_mean);
7348            denom += x * x;
7349        }
7350        if denom.abs() < 1e-10 { return 0.0; }
7351        numer / denom
7352    }
7353
7354    pub fn is_memory_critical(&self) -> bool {
7355        self.memory_utilization() > 0.9
7356    }
7357
7358    pub fn is_bandwidth_saturated(&self) -> bool {
7359        self.bandwidth_utilization > 0.95
7360    }
7361
7362    pub fn report_summary(&self) -> HashMap<String, f32> {
7363        let mut map = HashMap::new();
7364        map.insert("memory_mb".to_string(), self.current_memory_mb);
7365        map.insert("memory_utilization".to_string(), self.memory_utilization());
7366        map.insert("avg_load_ms".to_string(), self.avg_load_time_ms);
7367        map.insert("max_load_ms".to_string(), self.max_load_time_ms);
7368        map.insert("stalls".to_string(), self.streaming_stalls as f32);
7369        map.insert("total_loads".to_string(), self.total_loads as f32);
7370        map.insert("avg_fps".to_string(), self.average_fps());
7371        map.insert("memory_trend_mb_per_frame".to_string(), self.memory_trend());
7372        map
7373    }
7374}
7375
7376// ============================================================
7377// SECTION: Integrated Level Streaming World Manager (Complete)
7378// ============================================================
7379
7380#[derive(Clone, Debug)]
7381pub struct IntegratedStreamingWorld {
7382    pub scene_graph: SceneGraph,
7383    pub terrain: TerrainManager,
7384    pub tile_map: StreamingTileMap,
7385    pub instancer: LevelInstancer,
7386    pub fog_zones: Vec<FogZone>,
7387    pub deadline_scheduler: DeadlineScheduler,
7388    pub spatial_hash: SpatialHash3D,
7389    pub metrics: StreamingMetricsDashboard,
7390    pub camera_pos: Vec3,
7391    pub camera_dir: Vec3,
7392    pub camera_velocity: Vec3,
7393    pub current_time: f64,
7394    pub delta_time: f32,
7395    pub stream_radius_main: f32,
7396    pub stream_radius_secondary: f32,
7397    pub frame_count: u64,
7398}
7399
7400impl IntegratedStreamingWorld {
7401    pub fn new() -> Self {
7402        Self {
7403            scene_graph: SceneGraph::new(),
7404            terrain: TerrainManager::new(256.0, 5, 2000.0),
7405            tile_map: StreamingTileMap::new(512.0),
7406            instancer: LevelInstancer::new(256.0),
7407            fog_zones: Vec::new(),
7408            deadline_scheduler: DeadlineScheduler::new(100.0 * 1024.0 * 1024.0), // 100 MB/s
7409            spatial_hash: SpatialHash3D::new(128.0),
7410            metrics: StreamingMetricsDashboard::new(120),
7411            camera_pos: Vec3::ZERO,
7412            camera_dir: Vec3::NEG_Z,
7413            camera_velocity: Vec3::ZERO,
7414            current_time: 0.0,
7415            delta_time: 0.016,
7416            stream_radius_main: 800.0,
7417            stream_radius_secondary: 1500.0,
7418            frame_count: 0,
7419        }
7420    }
7421
7422    pub fn update(&mut self, camera_pos: Vec3, camera_dir: Vec3, delta_time: f32) {
7423        let prev_pos = self.camera_pos;
7424        self.camera_pos = camera_pos;
7425        self.camera_dir = camera_dir.normalize_or_zero();
7426        self.camera_velocity = (camera_pos - prev_pos) / delta_time.max(1e-6);
7427        self.delta_time = delta_time;
7428        self.current_time += delta_time as f64;
7429        self.frame_count += 1;
7430        // Update subsystems
7431        let fps = if delta_time > 1e-6 { 1.0 / delta_time } else { 60.0 };
7432        self.metrics.begin_frame(self.current_time, fps);
7433        self.terrain.update(camera_pos);
7434        self.deadline_scheduler.update(self.current_time, delta_time as f64);
7435        self.scene_graph.update_world_transforms();
7436        let mem_mb = self.estimate_memory_mb();
7437        self.metrics.update_memory(mem_mb);
7438    }
7439
7440    fn estimate_memory_mb(&self) -> f32 {
7441        let terrain_verts = self.terrain.total_vertices_rendered as f32 * 32.0; // 32 bytes/vert
7442        let scene_nodes = self.scene_graph.node_count() as f32 * 256.0;
7443        let instances = self.instancer.instances.len() as f32 * 512.0;
7444        let tiles = self.tile_map.tile_count() as f32 * 128.0;
7445        (terrain_verts + scene_nodes + instances + tiles) / (1024.0 * 1024.0)
7446    }
7447
7448    pub fn add_fog_zone(&mut self, bounds: Aabb) -> u32 {
7449        let id = self.fog_zones.len() as u32 + 1;
7450        self.fog_zones.push(FogZone::new(id, bounds));
7451        id
7452    }
7453
7454    pub fn fog_density_at(&self, pos: Vec3) -> f32 {
7455        self.fog_zones.iter()
7456            .filter(|z| z.enabled)
7457            .map(|z| z.density_at(pos, self.current_time))
7458            .sum()
7459    }
7460
7461    pub fn schedule_level_load(&mut self, level_id: u64, priority: f32, size_bytes: u64) -> u64 {
7462        let deadline = self.current_time + 2.0; // 2 second deadline
7463        let est_ms = size_bytes as f32 / (100.0 * 1024.0); // estimate at 100 MB/s
7464        self.deadline_scheduler.submit(level_id, priority, deadline, size_bytes, est_ms, self.current_time)
7465    }
7466
7467    pub fn place_level_instance(&mut self, level_id: u64, position: Vec3) -> u64 {
7468        let transform = Mat4::from_translation(position);
7469        let instance_id = self.instancer.place_instance(level_id, transform);
7470        self.spatial_hash.insert(instance_id, position);
7471        // Create scene node
7472        let node_id = self.scene_graph.create_node(&format!("Level_{}", instance_id));
7473        if let Some(node) = self.scene_graph.nodes.get_mut(&node_id) {
7474            node.local_transform = transform;
7475            node.world_transform = transform;
7476            node.level_id = Some(level_id);
7477            node.dirty = false;
7478        }
7479        instance_id
7480    }
7481
7482    pub fn instances_near(&self, center: Vec3, radius: f32) -> Vec<u64> {
7483        self.spatial_hash.query_radius(center, radius)
7484    }
7485
7486    pub fn terrain_height_at(&self, x: f32, z: f32) -> f32 {
7487        self.terrain.sample_height_world(x, z)
7488    }
7489
7490    pub fn predicted_camera_position(&self, look_ahead_seconds: f32) -> Vec3 {
7491        self.camera_pos + self.camera_velocity * look_ahead_seconds
7492            + Vec3::Y * 0.5 * (-9.8) * look_ahead_seconds * look_ahead_seconds
7493    }
7494
7495    pub fn tiles_to_preload(&self, look_ahead_seconds: f32) -> Vec<(i32, i32)> {
7496        let future_pos = self.predicted_camera_position(look_ahead_seconds);
7497        let mut tiles = self.tile_map.tiles_in_radius(future_pos, self.stream_radius_main);
7498        let current_tiles = self.tile_map.tiles_in_radius(self.camera_pos, self.stream_radius_main);
7499        tiles.retain(|t| !current_tiles.contains(t));
7500        tiles
7501    }
7502
7503    pub fn memory_mb(&self) -> f32 { self.metrics.current_memory_mb }
7504    pub fn is_critical(&self) -> bool { self.metrics.is_memory_critical() }
7505    pub fn frame_count(&self) -> u64 { self.frame_count }
7506    pub fn avg_fps(&self) -> f32 { self.metrics.average_fps() }
7507}
7508
7509// ============================================================
7510// SECTION: Additional Unit Tests
7511// ============================================================
7512
7513#[cfg(test)]
7514mod additional_tests {
7515    use super::*;
7516
7517    #[test]
7518    fn test_streaming_tile_bounds() {
7519        let tile = StreamingTile::new(2, 3, 100.0);
7520        let bounds = tile.world_bounds();
7521        assert!((bounds.min.x - 200.0).abs() < 0.01);
7522        assert!((bounds.max.x - 300.0).abs() < 0.01);
7523    }
7524
7525    #[test]
7526    fn test_streaming_tile_contains() {
7527        let tile = StreamingTile::new(0, 0, 100.0);
7528        assert!(tile.contains_point_2d(50.0, 50.0));
7529        assert!(!tile.contains_point_2d(150.0, 50.0));
7530    }
7531
7532    #[test]
7533    fn test_tile_map_world_to_tile() {
7534        let map = StreamingTileMap::new(128.0);
7535        let (tx, tz) = map.world_to_tile(Vec3::new(300.0, 0.0, 300.0));
7536        assert_eq!(tx, 2);
7537        assert_eq!(tz, 2);
7538    }
7539
7540    #[test]
7541    fn test_tile_map_tiles_in_radius() {
7542        let mut map = StreamingTileMap::new(100.0);
7543        map.get_or_create(0, 0).last_visited_time = 0.0;
7544        map.get_or_create(1, 0).last_visited_time = 0.0;
7545        map.get_or_create(0, 1).last_visited_time = 0.0;
7546        let tiles = map.tiles_in_radius(Vec3::new(50.0, 0.0, 50.0), 200.0);
7547        assert!(!tiles.is_empty());
7548    }
7549
7550    #[test]
7551    fn test_tile_map_evict_stale() {
7552        let mut map = StreamingTileMap::new(100.0);
7553        map.get_or_create(0, 0).last_visited_time = 0.0;
7554        map.get_or_create(1, 1).last_visited_time = 0.0;
7555        let evicted = map.evict_stale(100.0, 50.0);
7556        assert_eq!(evicted, 2);
7557        assert_eq!(map.tile_count(), 0);
7558    }
7559
7560    #[test]
7561    fn test_level_instance_position() {
7562        let t = Mat4::from_translation(Vec3::new(10.0, 5.0, -3.0));
7563        let inst = LevelInstance::new(1, 42, t);
7564        let pos = inst.position();
7565        assert!((pos.x - 10.0).abs() < 0.01);
7566        assert!((pos.y - 5.0).abs() < 0.01);
7567        assert!((pos.z + 3.0).abs() < 0.01);
7568    }
7569
7570    #[test]
7571    fn test_level_instancer_place_and_query() {
7572        let mut instancer = LevelInstancer::new(100.0);
7573        let t = Mat4::from_translation(Vec3::new(50.0, 0.0, 50.0));
7574        let id = instancer.place_instance(1, t);
7575        let found = instancer.instances_in_radius(Vec3::new(50.0, 0.0, 50.0), 10.0);
7576        assert!(found.contains(&id));
7577    }
7578
7579    #[test]
7580    fn test_level_instancer_remove() {
7581        let mut instancer = LevelInstancer::new(100.0);
7582        let t = Mat4::IDENTITY;
7583        let id = instancer.place_instance(5, t);
7584        assert!(instancer.instances.contains_key(&id));
7585        instancer.remove_instance(id);
7586        assert!(!instancer.instances.contains_key(&id));
7587    }
7588
7589    #[test]
7590    fn test_terrain_patch_height_sample() {
7591        let mut patch = TerrainPatch::new(1, 0, 0, 100.0, 5);
7592        // Fill with a known height
7593        for h in &mut patch.height_data { *h = 42.0; }
7594        let h = patch.sample_height_bilinear(50.0, 50.0);
7595        assert!((h - 42.0).abs() < 0.01);
7596    }
7597
7598    #[test]
7599    fn test_terrain_patch_lod_selection() {
7600        let mut patch = TerrainPatch::new(1, 0, 0, 256.0, 5);
7601        patch.update_lod(Vec3::new(128.0, 0.0, 128.0)); // near center
7602        assert_eq!(patch.current_lod, 0); // should be highest quality
7603        patch.update_lod(Vec3::new(10000.0, 0.0, 10000.0)); // very far
7604        assert!(patch.current_lod > 0);
7605    }
7606
7607    #[test]
7608    fn test_terrain_manager_update() {
7609        let mut mgr = TerrainManager::new(256.0, 4, 1000.0);
7610        mgr.update(Vec3::new(0.0, 0.0, 0.0));
7611        assert!(mgr.visible_patch_count() > 0);
7612    }
7613
7614    #[test]
7615    fn test_deadline_scheduler_submit_complete() {
7616        let mut sched = DeadlineScheduler::new(100.0 * 1024.0 * 1024.0);
7617        let id = sched.submit(1, 1.0, 2.0, 1024, 1.0, 0.0);
7618        sched.complete_request(id);
7619        assert_eq!(sched.total_completed, 1);
7620        assert!(sched.requests.is_empty());
7621    }
7622
7623    #[test]
7624    fn test_deadline_scheduler_miss_rate() {
7625        let mut sched = DeadlineScheduler::new(1.0);
7626        sched.submit(1, 1.0, 0.001, 1024, 1.0, 0.0);
7627        sched.update(1.0, 1.0); // past the deadline
7628        assert_eq!(sched.total_missed, 1);
7629    }
7630
7631    #[test]
7632    fn test_spatial_hash_insert_query() {
7633        let mut sh = SpatialHash3D::new(10.0);
7634        sh.insert(1, Vec3::new(5.0, 0.0, 5.0));
7635        sh.insert(2, Vec3::new(100.0, 0.0, 100.0));
7636        let result = sh.query_radius(Vec3::new(5.0, 0.0, 5.0), 5.0);
7637        assert!(result.contains(&1));
7638        assert!(!result.contains(&2));
7639    }
7640
7641    #[test]
7642    fn test_spatial_hash_update() {
7643        let mut sh = SpatialHash3D::new(10.0);
7644        sh.insert(1, Vec3::new(5.0, 0.0, 5.0));
7645        sh.update(1, Vec3::new(200.0, 0.0, 200.0));
7646        let near = sh.query_radius(Vec3::new(5.0, 0.0, 5.0), 5.0);
7647        assert!(!near.contains(&1));
7648        let far = sh.query_radius(Vec3::new(200.0, 0.0, 200.0), 5.0);
7649        assert!(far.contains(&1));
7650    }
7651
7652    #[test]
7653    fn test_fog_zone_density() {
7654        let bounds = Aabb::new(Vec3::new(-100.0, -50.0, -100.0), Vec3::new(100.0, 50.0, 100.0));
7655        let zone = FogZone::new(1, bounds);
7656        let d = zone.density_at(Vec3::new(0.0, 0.0, 0.0), 0.0);
7657        assert!(d >= 0.0 && d.is_finite());
7658        let d_outside = zone.density_at(Vec3::new(1000.0, 0.0, 0.0), 0.0);
7659        assert_eq!(d_outside, 0.0);
7660    }
7661
7662    #[test]
7663    fn test_fog_transmittance() {
7664        let bounds = Aabb::new(Vec3::new(-200.0, -100.0, -200.0), Vec3::new(200.0, 100.0, 200.0));
7665        let zone = FogZone::new(1, bounds);
7666        let t = zone.transmittance_along_ray(
7667            Vec3::new(-100.0, 0.0, 0.0),
7668            Vec3::new(100.0, 0.0, 0.0),
7669            16,
7670            0.0,
7671        );
7672        assert!(t > 0.0 && t <= 1.0);
7673    }
7674
7675    #[test]
7676    fn test_henyey_greenstein_forward() {
7677        // Forward scattering (cos_theta=1): should be maximum
7678        let g = 0.8;
7679        let forward = FogZone::phase_function_henyey_greenstein(1.0, g);
7680        let backward = FogZone::phase_function_henyey_greenstein(-1.0, g);
7681        assert!(forward > backward);
7682    }
7683
7684    #[test]
7685    fn test_scene_graph_attach() {
7686        let mut sg = SceneGraph::new();
7687        let parent = sg.create_node("parent");
7688        let child = sg.create_node("child");
7689        sg.attach_child(parent, child);
7690        assert!(sg.nodes[&parent].children.contains(&child));
7691        assert_eq!(sg.nodes[&child].parent_id, Some(parent));
7692        assert!(!sg.root_nodes.contains(&child));
7693    }
7694
7695    #[test]
7696    fn test_scene_graph_world_transform() {
7697        let mut sg = SceneGraph::new();
7698        let parent = sg.create_node("parent");
7699        let child = sg.create_node("child");
7700        sg.attach_child(parent, child);
7701        let t_parent = Mat4::from_translation(Vec3::new(10.0, 0.0, 0.0));
7702        let t_child = Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0));
7703        sg.nodes.get_mut(&parent).unwrap().local_transform = t_parent;
7704        sg.nodes.get_mut(&parent).unwrap().world_transform = t_parent;
7705        sg.nodes.get_mut(&child).unwrap().local_transform = t_child;
7706        sg.nodes.get_mut(&child).unwrap().dirty = true;
7707        sg.update_world_transforms();
7708        let child_pos = sg.nodes[&child].world_transform.w_axis.x;
7709        assert!((child_pos - 15.0).abs() < 0.01);
7710    }
7711
7712    #[test]
7713    fn test_scene_graph_depth() {
7714        let mut sg = SceneGraph::new();
7715        let a = sg.create_node("a");
7716        let b = sg.create_node("b");
7717        let c = sg.create_node("c");
7718        sg.attach_child(a, b);
7719        sg.attach_child(b, c);
7720        assert_eq!(sg.depth_of(c), 2);
7721        assert_eq!(sg.depth_of(a), 0);
7722    }
7723
7724    #[test]
7725    fn test_scene_graph_subtree_removal() {
7726        let mut sg = SceneGraph::new();
7727        let a = sg.create_node("a");
7728        let b = sg.create_node("b");
7729        let c = sg.create_node("c");
7730        sg.attach_child(a, b);
7731        sg.attach_child(b, c);
7732        let removed = sg.remove_subtree(b);
7733        assert_eq!(removed, 2); // b and c
7734        assert!(sg.nodes.contains_key(&a));
7735        assert!(!sg.nodes.contains_key(&b));
7736        assert!(!sg.nodes.contains_key(&c));
7737    }
7738
7739    #[test]
7740    fn test_metrics_dashboard_memory_trend() {
7741        let mut dash = StreamingMetricsDashboard::new(30);
7742        for i in 0..20 {
7743            dash.begin_frame(i as f64 * 0.016, 60.0);
7744            dash.update_memory(100.0 + i as f32 * 2.0); // linearly increasing memory
7745        }
7746        let trend = dash.memory_trend();
7747        assert!(trend > 0.0, "Memory trend should be positive (increasing): {}", trend);
7748    }
7749
7750    #[test]
7751    fn test_metrics_dashboard_critical() {
7752        let mut dash = StreamingMetricsDashboard::new(10);
7753        dash.memory_budget_mb = 100.0;
7754        dash.update_memory(95.0);
7755        assert!(dash.is_memory_critical());
7756        dash.update_memory(80.0);
7757        assert!(!dash.is_memory_critical());
7758    }
7759
7760    #[test]
7761    fn test_integrated_world_update() {
7762        let mut world = IntegratedStreamingWorld::new();
7763        world.update(Vec3::new(100.0, 10.0, 100.0), Vec3::NEG_Z, 0.016);
7764        assert!(world.frame_count == 1);
7765        assert!(world.current_time > 0.0);
7766    }
7767
7768    #[test]
7769    fn test_integrated_world_fog() {
7770        let mut world = IntegratedStreamingWorld::new();
7771        let bounds = Aabb::new(Vec3::new(-500.0, -200.0, -500.0), Vec3::new(500.0, 200.0, 500.0));
7772        world.add_fog_zone(bounds);
7773        let density = world.fog_density_at(Vec3::ZERO);
7774        assert!(density >= 0.0 && density.is_finite());
7775    }
7776
7777    #[test]
7778    fn test_integrated_world_instance_placement() {
7779        let mut world = IntegratedStreamingWorld::new();
7780        let id = world.place_level_instance(42, Vec3::new(100.0, 0.0, 100.0));
7781        let near = world.instances_near(Vec3::new(100.0, 0.0, 100.0), 50.0);
7782        assert!(near.contains(&id));
7783    }
7784
7785    #[test]
7786    fn test_integrated_world_prediction() {
7787        let mut world = IntegratedStreamingWorld::new();
7788        world.camera_velocity = Vec3::new(10.0, 0.0, 0.0);
7789        let future = world.predicted_camera_position(1.0);
7790        // At t=1s: x should advance ~10 units
7791        assert!((future.x - world.camera_pos.x - 10.0).abs() < 0.1);
7792    }
7793
7794    #[test]
7795    fn test_terrain_patch_normal() {
7796        let mut patch = TerrainPatch::new(1, 0, 0, 100.0, 5);
7797        // Flat terrain: normal should point straight up
7798        for h in &mut patch.height_data { *h = 0.0; }
7799        let normal = patch.compute_normal_at(50.0, 50.0);
7800        assert!((normal.y - 1.0).abs() < 0.01);
7801    }
7802
7803    #[test]
7804    fn test_terrain_patch_sloped_normal() {
7805        let mut patch = TerrainPatch::new(1, 0, 0, 100.0, 5);
7806        let res = patch.height_grid_res as usize;
7807        // Create an X-slope
7808        for z in 0..res {
7809            for x in 0..res {
7810                patch.height_data[z * res + x] = x as f32 * 1.0;
7811            }
7812        }
7813        let normal = patch.compute_normal_at(50.0, 50.0);
7814        // Should have negative X component (slope going up in +X)
7815        assert!(normal.x < 0.0);
7816        assert!(normal.is_finite());
7817    }
7818
7819    #[test]
7820    fn test_scene_graph_find_by_name() {
7821        let mut sg = SceneGraph::new();
7822        let _a = sg.create_node("alpha");
7823        let _b = sg.create_node("beta");
7824        let found = sg.find_by_name("beta");
7825        assert!(found.is_some());
7826        let not_found = sg.find_by_name("gamma");
7827        assert!(not_found.is_none());
7828    }
7829
7830    #[test]
7831    fn test_deadline_scheduler_batch() {
7832        let mut sched = DeadlineScheduler::new(100.0 * 1024.0 * 1024.0);
7833        for i in 0..10 {
7834            sched.submit(i, 1.0, 2.0, 1024 * 1024, 10.0, 0.0);
7835        }
7836        let batch = sched.next_batch(0.0, 5);
7837        assert!(!batch.is_empty());
7838        assert!(batch.len() <= 5);
7839    }
7840
7841    #[test]
7842    fn test_spatial_hash_aabb_query() {
7843        let mut sh = SpatialHash3D::new(10.0);
7844        sh.insert(1, Vec3::new(5.0, 0.0, 5.0));
7845        sh.insert(2, Vec3::new(50.0, 0.0, 50.0));
7846        let aabb = Aabb::new(Vec3::new(0.0, -5.0, 0.0), Vec3::new(10.0, 5.0, 10.0));
7847        let result = sh.query_aabb(&aabb);
7848        assert!(result.contains(&1));
7849    }
7850
7851    #[test]
7852    fn test_level_instancer_count_by_level() {
7853        let mut instancer = LevelInstancer::new(100.0);
7854        instancer.place_instance(1, Mat4::IDENTITY);
7855        instancer.place_instance(1, Mat4::from_translation(Vec3::X));
7856        instancer.place_instance(2, Mat4::from_translation(Vec3::Y));
7857        let counts = instancer.count_by_level();
7858        assert_eq!(counts[&1], 2);
7859        assert_eq!(counts[&2], 1);
7860    }
7861
7862    #[test]
7863    fn test_fog_zone_disabled() {
7864        let bounds = Aabb::new(Vec3::splat(-100.0), Vec3::splat(100.0));
7865        let mut zone = FogZone::new(1, bounds);
7866        zone.enabled = false;
7867        let d = zone.density_at(Vec3::ZERO, 0.0);
7868        assert_eq!(d, 0.0);
7869    }
7870
7871    #[test]
7872    fn test_terrain_patch_stitch_skirt_nonempty() {
7873        let patch = TerrainPatch::new(1, 0, 0, 100.0, 3);
7874        let skirt = patch.stitch_skirt_vertices();
7875        assert!(!skirt.is_empty());
7876    }
7877
7878    #[test]
7879    fn test_tile_map_add_remove_level() {
7880        let mut map = StreamingTileMap::new(100.0);
7881        map.add_level_to_tile(0, 0, 99);
7882        assert!(map.tiles.get(&(0, 0)).map(|t| t.level_ids.contains(&99)).unwrap_or(false));
7883        map.remove_level_from_all(99);
7884        assert!(map.tiles.get(&(0, 0)).map(|t| t.level_ids.is_empty()).unwrap_or(true));
7885    }
7886
7887    #[test]
7888    fn test_deadline_scheduler_utilization() {
7889        let sched = DeadlineScheduler::new(1024.0);
7890        assert!((sched.utilization() - 0.0).abs() < 0.01);
7891    }
7892
7893    #[test]
7894    fn test_spatial_hash_average_occupancy() {
7895        let mut sh = SpatialHash3D::new(10.0);
7896        sh.insert(1, Vec3::ZERO);
7897        sh.insert(2, Vec3::new(1.0, 0.0, 0.0));
7898        let occ = sh.average_occupancy();
7899        assert!(occ >= 1.0 && occ.is_finite());
7900    }
7901
7902    #[test]
7903    fn test_integrated_world_terrain_height() {
7904        let mut world = IntegratedStreamingWorld::new();
7905        world.update(Vec3::ZERO, Vec3::NEG_Z, 0.016);
7906        let h = world.terrain_height_at(50.0, 50.0);
7907        assert!(h.is_finite());
7908    }
7909
7910    #[test]
7911    fn test_metrics_dashboard_report() {
7912        let mut dash = StreamingMetricsDashboard::new(10);
7913        dash.record_load(10.0, 1024 * 1024);
7914        dash.record_unload(512 * 1024);
7915        let report = dash.report_summary();
7916        assert!(report.contains_key("avg_load_ms"));
7917        assert!((report["avg_load_ms"] - 10.0).abs() < 0.01);
7918    }
7919
7920    #[test]
7921    fn test_scene_graph_collect_subtree() {
7922        let mut sg = SceneGraph::new();
7923        let a = sg.create_node("a");
7924        let b = sg.create_node("b");
7925        let c = sg.create_node("c");
7926        sg.attach_child(a, b);
7927        sg.attach_child(a, c);
7928        let sub = sg.collect_subtree(a);
7929        assert_eq!(sub.len(), 3);
7930        assert!(sub.contains(&a) && sub.contains(&b) && sub.contains(&c));
7931    }
7932
7933    #[test]
7934    fn test_level_instance_tags() {
7935        let mut inst = LevelInstance::new(1, 1, Mat4::IDENTITY);
7936        inst.add_tag("outdoor");
7937        inst.add_tag("night");
7938        assert!(inst.has_tag("outdoor"));
7939        assert!(!inst.has_tag("indoor"));
7940    }
7941
7942    #[test]
7943    fn test_level_instance_scale() {
7944        let t = Mat4::from_scale(Vec3::new(2.0, 3.0, 4.0));
7945        let inst = LevelInstance::new(1, 1, t);
7946        let scale = inst.scale();
7947        assert!((scale.x - 2.0).abs() < 0.01);
7948        assert!((scale.y - 3.0).abs() < 0.01);
7949        assert!((scale.z - 4.0).abs() < 0.01);
7950    }
7951
7952    #[test]
7953    fn test_terrain_patch_vertex_count_at_lod() {
7954        let mut patch = TerrainPatch::new(1, 0, 0, 256.0, 4);
7955        // LOD 0: full res
7956        patch.update_lod(Vec3::new(128.0, 0.0, 128.0));
7957        let verts_lod0 = patch.vertex_count;
7958        // Move far away to trigger high LOD
7959        patch.update_lod(Vec3::new(10000.0, 0.0, 10000.0));
7960        let verts_lod_high = patch.vertex_count;
7961        assert!(verts_lod0 >= verts_lod_high, "Lower LOD should have fewer vertices");
7962    }
7963
7964    #[test]
7965    fn test_integrated_world_schedule_load() {
7966        let mut world = IntegratedStreamingWorld::new();
7967        let req_id = world.schedule_level_load(7, 1.0, 4 * 1024 * 1024);
7968        assert!(req_id > 0);
7969        assert!(!world.deadline_scheduler.requests.is_empty());
7970    }
7971
7972    #[test]
7973    fn test_fog_zone_height_falloff() {
7974        let bounds = Aabb::new(Vec3::new(-200.0, -100.0, -200.0), Vec3::new(200.0, 1000.0, 200.0));
7975        let mut zone = FogZone::new(1, bounds);
7976        zone.height_falloff = 0.1;
7977        zone.turbulence = 0.0;
7978        let d_low  = zone.density_at(Vec3::new(0.0, 0.0, 0.0), 0.0);
7979        let d_high = zone.density_at(Vec3::new(0.0, 100.0, 0.0), 0.0);
7980        assert!(d_low >= d_high, "Density should decrease with height");
7981    }
7982
7983    #[test]
7984    fn test_spatial_hash_remove() {
7985        let mut sh = SpatialHash3D::new(10.0);
7986        sh.insert(10, Vec3::ZERO);
7987        sh.remove(10);
7988        assert_eq!(sh.object_count(), 0);
7989        assert!(sh.query_radius(Vec3::ZERO, 5.0).is_empty());
7990    }
7991}