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;
}
}