UI/Player/ScannerSceneController.cs
using Sandbox;

public sealed class ScannerSceneController : Component
{
	public sealed class BlipData
	{
		public Vector3 Position { get; set; }
		public bool Above { get; set; }
		public bool IsBot { get; set; }
	}

	private sealed class BlipVisual
	{
		public GameObject Arrow { get; set; }
		public ModelRenderer ArrowRenderer { get; set; }
		public GameObject Line { get; set; }
		public LineRenderer LineRenderer { get; set; }
	}

	private readonly List<BlipVisual> _blipVisuals = new();
	private IReadOnlyList<BlipData> _blips = Array.Empty<BlipData>();
	private GameObject _playerMarker;
	private ModelRenderer _playerMarkerRenderer;
	private GameObject _radarBase;
	private ModelRenderer _radarBaseRenderer;

	public void SetBlips( IReadOnlyList<BlipData> blips )
	{
		_blips = blips ?? Array.Empty<BlipData>();
	}

	protected override void OnUpdate()
	{
		EnsureRadarBase();
		EnsurePlayerMarker();
		EnsureBlipObjects();
		RenderBlips();
	}

	private void EnsurePlayerMarker()
	{
		if ( _playerMarker != null && _playerMarkerRenderer != null )
			return;

		_playerMarker = Scene.CreateObject();
		_playerMarkerRenderer = _playerMarker.Components.Create<ModelRenderer>();
		_playerMarkerRenderer.Model = Model.Load( "models/ui/radar_ship_01.vmdl" );
		_playerMarkerRenderer.RenderOptions.Overlay = true;
		_playerMarkerRenderer.Tint = new Color( 1f, 0.2f, 0.2f, 0.95f );
		_playerMarker.WorldPosition = Vector3.Zero;
	}

	private void EnsureRadarBase()
	{
		if ( _radarBase != null && _radarBaseRenderer != null )
			return;

		_radarBase = Scene.CreateObject();
		_radarBaseRenderer = _radarBase.Components.Create<ModelRenderer>();
		_radarBaseRenderer.Model = Model.Load( "models/dev/plane.vmdl" );
		_radarBaseRenderer.MaterialOverride = Material.Load( "materials/ui/radar_01.vmat" );
		_radarBaseRenderer.SceneObject.Flags.IsTranslucent = true;
		_radarBaseRenderer.RenderOptions.Overlay = true;

	}

	private void EnsureBlipObjects()
	{
		while ( _blipVisuals.Count < _blips.Count )
		{
			var arrow = Scene.CreateObject();
			var arrowRenderer = arrow.Components.Create<ModelRenderer>();
			arrowRenderer.Model = Model.Load( "models/ui/radar_arrow_01.vmdl" );
			arrowRenderer.RenderOptions.Overlay = true;

			var line = Scene.CreateObject();
			var lineRenderer = line.Components.Create<LineRenderer>();
			lineRenderer.RenderOptions.Overlay = true;
			lineRenderer.UseVectorPoints = true;
			lineRenderer.Width = 0.7f;
			lineRenderer.Lighting = false;
			lineRenderer.CastShadows = false;
			lineRenderer.VectorPoints = new List<Vector3> { Vector3.Zero, Vector3.Zero };

			_blipVisuals.Add( new BlipVisual
			{
				Arrow = arrow,
				ArrowRenderer = arrowRenderer,
				Line = line,
				LineRenderer = lineRenderer
			} );
		}
	}

	private void RenderBlips()
	{
		for ( int i = 0; i < _blipVisuals.Count; i++ )
		{
			var v = _blipVisuals[i];
			if ( v?.Arrow == null || v.Line == null || v.ArrowRenderer == null || v.LineRenderer == null )
				continue;

			if ( i >= _blips.Count )
			{
				v.ArrowRenderer.Enabled = false;
				v.LineRenderer.Enabled = false;
				continue;
			}

			var b = _blips[i];
			v.ArrowRenderer.Enabled = true;
			v.LineRenderer.Enabled = true;

			v.Arrow.WorldPosition = b.Position;
			// Keep all arrows in one constant orientation
			v.Arrow.WorldRotation = Rotation.LookAt( b.Above ? Vector3.Up : Vector3.Down, Vector3.Left );
			v.Arrow.WorldScale = b.Above ? Vector3.One * 0.12f : Vector3.One * 0.1f;

			var color = b.Above
				? new Color( 0.62f, 0.92f, 1f, 0.95f )
				: new Color( 1f, 0.58f, 0.4f, 0.9f );

			if ( b.IsBot )
			{
				color = b.Above
					? new Color( 0.55f, 0.8f, 1f, 0.95f )
					: new Color( 1f, 0.45f, 0.8f, 0.9f );
			}

			v.ArrowRenderer.Tint = color;
			v.LineRenderer.Color = color.WithAlpha( 0.7f );
			v.LineRenderer.VectorPoints ??= new List<Vector3>();
			v.LineRenderer.VectorPoints.Clear();
			v.LineRenderer.VectorPoints.Add( b.Position );
			// Height line endpoint at z=0 (player plane in scanner coordinates)
			v.LineRenderer.VectorPoints.Add( new Vector3( b.Position.x, b.Position.y, 0f ) );
		}
	}

	protected override void OnDestroy()
	{
		foreach ( var v in _blipVisuals )
		{
			v?.Arrow?.Destroy();
			v?.Line?.Destroy();
		}

		_playerMarker?.Destroy();
		_playerMarker = null;
		_playerMarkerRenderer = null;
		_radarBase?.Destroy();
		_radarBase = null;
		_radarBaseRenderer = null;

		_blipVisuals.Clear();
		base.OnDestroy();
	}
}