UI/OptionsBackdrop.razor

UI panel component that renders a drifting, parallaxed backdrop of square blocks for the options overlay. It imperatively creates Panel children, advances their positions each Tick, bounces them inside a 240x240 logical arena, and converts preset linear colors to gamma for visual match.

Obfuscated Code
@using System
@using System.Collections.Generic
@using Sandbox
@using Sandbox.UI
@inherits Panel
@namespace BlockParty

@* Drifting cosmetic background blocks for the in-run / in-replay options overlay, redrawn in UI space.
   The arena behind the overlay is FROZEN (the sim is paused while the menu is open), so its live
   drifting blocks stop; this panel re-creates the main-menu options screen's moving-block look purely
   in the overlay. Three parallax layers of stepped teal squares drift on cardinal paths and bounce off
   the panel bounds. Created + animated imperatively (see Tick / BuildBlocks) so it never rebuilds the
   panel. Fills its parent (the overlay's checkerboard panel) and sits BEHIND the options labels.

   All critical positioning is INLINE (root absolute-fill, blocks Position=Absolute set in C#) rather
   than relying on a freshly-added component's .scss, which can fail to apply on the first render. *@
<root style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden; pointer-events: none;"></root>

@code
{
	// A single drifting square: logical arena-space position/velocity (origin bottom-left, +Y up) plus
	// its size, shade and depth layer. Panels are moved (not rebuilt) each frame.
	private sealed class BgBlock
	{
		public Panel Panel;
		public Vector2 Pos;   // logical arena units (0..240)
		public Vector2 Vel;   // logical units / second (purely cardinal)
		public float Size;    // logical units
		public Color Color;
		public int Depth;     // 0 = nearest/lightest, 2 = deepest/darkest
	}

	private readonly List<BgBlock> _blocks = new();
	private bool _blocksBuilt;

	// Session-cosmetic only (never gameplay / replay), so a plain non-deterministic RNG is fine.
	private readonly Random _rng = new();

	// The default teal drifting-block shades (front -> back), matching StageBase.SpawnBackgroundBlocks.
	// In-game these feed the sprite tint as LINEAR (no gamma->linear pre-convert), so the display
	// gamma-encodes them and they read BRIGHTER on screen; the UI paints in gamma space, so mirror that
	// brightening (linear->gamma) here so the overlay blocks match the colours the game actually shows.
	private static readonly Color[] Shades =
	{
		LinearToGamma( new Color( 0f, 150f / 255f, 157f / 255f ) ),
		LinearToGamma( new Color( 0f, 135f / 255f, 142f / 255f ) ),
		LinearToGamma( new Color( 0f, 120f / 255f, 127f / 255f ) ),
	};

	// Per-frame hook: advance each block along its cardinal velocity and bounce it off the panel bounds.
	public override void Tick()
	{
		base.Tick();

		// (Re)build on first tick, or when a render pass (hot-reload / reconcile) has WIPED the
		// imperatively-created child panels — they aren't in the declarative template, so a render can
		// delete them while _blocks still holds stale refs.
		if ( !_blocksBuilt || ChildrenCount != _blocks.Count )
			BuildBlocks();

		float dt = Time.Delta;
		foreach ( var b in _blocks )
		{
			var p = b.Pos + b.Vel * dt;
			float maxX = Arena.WIDTH - b.Size;
			float maxY = Arena.HEIGHT - b.Size;

			// Bounce off the square bounds by flipping the offending axis.
			if ( p.x < 0f ) { p.x = 0f; b.Vel = new Vector2( MathF.Abs( b.Vel.x ), b.Vel.y ); }
			else if ( p.x > maxX ) { p.x = maxX; b.Vel = new Vector2( -MathF.Abs( b.Vel.x ), b.Vel.y ); }

			if ( p.y < 0f ) { p.y = 0f; b.Vel = new Vector2( b.Vel.x, MathF.Abs( b.Vel.y ) ); }
			else if ( p.y > maxY ) { p.y = maxY; b.Vel = new Vector2( b.Vel.x, -MathF.Abs( b.Vel.y ) ); }

			b.Pos = p;
			PositionBlock( b );
		}
	}

	// (Re)create the drifting-block panels: three parallax layers (6/5/4 blocks, sizes 50/42/34),
	// mirroring StageBase.SpawnBackgroundBlocks. Deeper layers are darker and drift a touch slower.
	private void BuildBlocks()
	{
		_blocks.Clear();
		DeleteChildren( true );
		_blocksBuilt = true;

		var built = new List<BgBlock>();
		for ( int layer = 0; layer < 3; layer++ )
		{
			int numBlocks = 6 - layer;
			float size = 50f - 8f * layer;
			for ( int j = 0; j < numBlocks; j++ )
			{
				// Spawn fully inside the square.
				float x = NextFloat() * ( Arena.WIDTH - size );
				float y = NextFloat() * ( Arena.HEIGHT - size );

				// Gentle cardinal drift; deeper layers move slightly slower (subtle parallax).
				float speed = ( 20f - layer * 4f ) + NextFloat() * 12f;
				Vector2 vel = NextFloat() < 0.5f
					? new Vector2( NextFloat() < 0.5f ? -speed : speed, 0f )
					: new Vector2( 0f, NextFloat() < 0.5f ? -speed : speed );

				built.Add( new BgBlock
				{
					Pos = new Vector2( x, y ),
					Vel = vel,
					Size = size,
					Color = Shades[layer],
					Depth = layer,
				} );
			}
		}

		// Darkest (deepest) first so lighter, nearer blocks paint on top.
		built.Sort( ( a, b ) => b.Depth.CompareTo( a.Depth ) );

		foreach ( var b in built )
		{
			b.Panel = new Panel( this, "bg-block" );
			b.Panel.Style.Position = PositionMode.Absolute;
			b.Panel.Style.Width = Length.Percent( b.Size / Arena.WIDTH * 100f );
			b.Panel.Style.Height = Length.Percent( b.Size / Arena.HEIGHT * 100f );
			b.Panel.Style.BackgroundColor = b.Color;
			_blocks.Add( b );
			PositionBlock( b );
		}
	}

	private static void PositionBlock( BgBlock b )
	{
		if ( b.Panel is null ) return;
		// Logical (bottom-left origin, +Y up) -> CSS percentages of the 240x240 square.
		b.Panel.Style.Left = Length.Percent( b.Pos.x / Arena.WIDTH * 100f );
		b.Panel.Style.Top = Length.Percent( ( Arena.HEIGHT - b.Pos.y - b.Size ) / Arena.HEIGHT * 100f );
	}

	// xorshift-free simple [0,1); plain Random is fine for a cosmetic UI layer.
	private float NextFloat() => (float)_rng.NextDouble();

	// The overlay never changes structurally, so it only needs to build once; the drifting is driven
	// imperatively in Tick, not by rebuilds.
	protected override int BuildHash() => 0;

	private static Color LinearToGamma( Color c ) => new Color( L2G( c.r ), L2G( c.g ), L2G( c.b ), c.a );

	// Standard sRGB linear->gamma transfer (matches what the display applies to the shader's linear output).
	private static float L2G( float v ) =>
		v <= 0.0031308f ? v * 12.92f : 1.055f * MathF.Pow( v, 1f / 2.4f ) - 0.055f;
}