using System.Collections.Generic; using System.Numerics; using AcDream.Core.World; namespace AcDream.App.Rendering; /// /// Splits a frame's landblock entities into the draw buckets used by the /// retail-style DrawInside flood. /// /// T1 (fused BR-2/3, 2026-06-11) — retail draw-order contract: the /// frame draws STATIC world first (terrain, building shells, scenery, then /// flooded interior cells + their static object lists), and every DYNAMIC /// (server-spawned: player, NPCs, doors, items) draws LAST, depth-tested, /// never hard-clipped. This is what makes the aperture depth punch safe — /// when the punch erases depth inside a doorway, no dynamic has been drawn /// yet, so nothing visible is destroyed (retail: objects draw per cell AFTER /// cells, PView::DrawCells epilogue Ghidra 0x005a4840; the first BR-2 attempt /// punched after dynamics and erased the player, reverted 88be519). /// /// /// — indoor STATICS (dat-baked, ServerGuid==0) /// per visible cell, drawn with their cell. /// — outdoor statics (building /// shells, scenery stabs), drawn with the world/landscape pass. /// — ALL server-spawned entities /// (ServerGuid != 0) regardless of cell, plus unresolved-cell live entities; /// drawn in the frame's single LAST entity pass. /// /// public static class InteriorEntityPartition { public sealed class Result { public Dictionary> ByCell { get; } = new(); public List OutdoorStatic { get; } = new(); public List Dynamics { get; } = new(); } public static Result Partition( HashSet visibleCells, IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList Entities, IReadOnlyDictionary? AnimatedById)> landblockEntries) { var result = new Result(); foreach (var entry in landblockEntries) { foreach (var e in entry.Entities) { if (e.MeshRefs.Count == 0) continue; // Retail contract: every server-spawned entity is a DYNAMIC // and draws in the last pass — indoor, outdoor, or unresolved. if (e.ServerGuid != 0) { result.Dynamics.Add(e); } else if (e.ParentCellId is uint cell && IsIndoorCellId(cell)) { if (!visibleCells.Contains(cell)) continue; if (!result.ByCell.TryGetValue(cell, out var list)) result.ByCell[cell] = list = new List(); list.Add(e); } else { result.OutdoorStatic.Add(e); } } } return result; } private static bool IsIndoorCellId(uint cellId) { uint low = cellId & 0xFFFFu; return low >= 0x0100u && low != 0xFFFFu; } }