Rendering/GridBackdrop.cs
using System;

/// <summary>
/// 背景网格 + 发光边界(静态几何,line.shader 直线网格)。
/// warp_grid.shader(正弦扭曲网格,Assets/shaders/ 下保留)曾实装并被用户喊停——先回退静态网格,
/// 该 shader 文件留着以后想再开时直接换回(见 PLAN.md M4 记录)。
/// 场地尺寸可被 cr_arena_size 改(Replicated 下发,可能晚于 OnStart 到达)——OnUpdate 检测变化重建几何。
/// </summary>
public sealed class GridBackdrop : Component
{
	/// <summary> 全局引用(cr_perf 渲染 A/B 用;NeonRenderer.Instance 同款) </summary>
	public static GridBackdrop Instance { get; private set; }

	SceneDynamicObject _lines;
	Material _lineMat;   // 材质强引用(托管包装被 GC 会连带回收原生材质 → 网格画成错误材质红几何,实测)
	float _builtHalf;   // 当前几何对应的场地半宽(变了就重建)

	// 像素风(v0.7.8.36):浅蓝底上的淡网格 + 柔和边界(原霓虹深蓝/亮青作废)
	static readonly Color GridColor = new Color( 0.847f, 0.910f, 0.965f, 0.9f );    // #D8E8F6
	static readonly Color BorderColor = new Color( 0.549f, 0.749f, 0.902f, 0.9f );  // 柔和浅蓝

	protected override void OnStart()
	{
		base.OnStart();
		Instance = this;
		Rebuild();
	}

	protected override void OnDestroy()
	{
		base.OnDestroy();

		// 组件销毁时必须显式删除原生批次:世界重建只销毁子 GameObject,遗留批次会带最后一帧
		// 几何继续渲染,旧材质回收后回退错误材质——满屏红几何 + Vulkan 管线崩溃的元凶(实测)
		if ( _lines is not null )
		{
			try { _lines.Delete(); } catch { }
			_lines = null;
		}
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();
		if ( !Game.IsPlaying ) return;

		// 客户端收到 Replicated 的场地尺寸(可能晚于 OnStart)→ 重建几何
		if ( MathF.Abs( _builtHalf - GameConfig.ArenaHalfSize ) > 0.5f )
			Rebuild();
	}

	void Rebuild()
	{
		var half = GameConfig.ArenaHalfSize;
		_builtHalf = half;

		if ( !_lines.IsValid() )
		{
			_lines = new SceneDynamicObject( Scene.SceneWorld );
			_lineMat = LineMaterial.Create();
			_lines.Material = _lineMat;
			_lines.Flags.CastShadows = false;
			_lines.RenderLayer = SceneRenderLayer.OverlayWithDepth;
			_lines.Attributes.SetCombo( "D_BLEND", 1 );
		}
		_lines.Clear();
		_lines.Init( Graphics.PrimitiveType.Lines );

		var step = GameConfig.GridStep;
		for ( var x = -half; x <= half + 1f; x += step )
		{
			AddLine( new Vector3( x, -half, 0f ), new Vector3( x, half, 0f ), GridColor );
		}
		for ( var y = -half; y <= half + 1f; y += step )
		{
			AddLine( new Vector3( -half, y, 0f ), new Vector3( half, y, 0f ), GridColor );
		}

		AddSquare( half - 2f, BorderColor );
		AddSquare( half + 10f, BorderColor * 0.55f );
	}

	void AddSquare( float half, Color color )
	{
		AddLine( new Vector3( -half, -half, 0.5f ), new Vector3( half, -half, 0.5f ), color );
		AddLine( new Vector3( half, -half, 0.5f ), new Vector3( half, half, 0.5f ), color );
		AddLine( new Vector3( half, half, 0.5f ), new Vector3( -half, half, 0.5f ), color );
		AddLine( new Vector3( -half, half, 0.5f ), new Vector3( -half, -half, 0.5f ), color );
	}

	void AddLine( Vector3 a, Vector3 b, Color color )
	{
		_lines.AddVertex( new Vertex( a, color ) );
		_lines.AddVertex( new Vertex( b, color ) );
	}

	/// <summary> 调试 A/B(v0.7.8.104,cr_perf 渲染减负定位):隐藏/恢复网格批次
	/// (组件 Enabled 关不掉已构建的原生批次——SceneObject.RenderingEnabled 才是真开关) </summary>
	public void SetDebugVisible( bool visible )
	{
		if ( _lines is not null ) _lines.RenderingEnabled = visible;
	}
}