UI/UiRig.cs

Static UI rig builder for the VehicleProto project. Mount creates and wires all HUD components (VehicleHud, TelemetryPanel, TuningPanel, overlays, etc.) under a given GameObject and resets UI static buffers; Retarget repoints already-mounted HUD components in a Scene to a new VehicleController after a live car swap.

Native Interop
namespace VehicleProto;

/// <summary>
/// Single entry point that builds and wires the whole HUD stack. GameBootstrap calls
/// <see cref="Mount"/> in place of hand-wiring each panel, so the panel roster lives in
/// one place. All four PanelComponents share one ScreenPanel GameObject (ui-razor.md).
/// </summary>
public static class UiRig
{
	/// <summary>
	/// Create the HUD host under <paramref name="hudGo"/> (or a fresh child) and wire every
	/// panel to <paramref name="target"/>. Resets the static UI buffers so nothing survives
	/// a Play→Stop→Play cycle in the editor.
	/// </summary>
	public static void Mount( GameObject hudGo, VehicleController target )
	{
		UiState.Reset();
		UiFeed.Reset();
		TuneRenameState.Reset();

		hudGo.Components.GetOrCreate<ScreenPanel>();

		var hud = hudGo.Components.Create<VehicleHud>();
		hud.Target = target;

		var telemetry = hudGo.Components.Create<TelemetryPanel>();
		telemetry.Target = target;

		// Live launch timer for the PLAYER — feeds the telemetry TIMING card during free driving
		// (the pilot only feeds it during scripted maneuvers). A plain always-on Component, not a
		// panel; it follows the active car like the panels do (Mount + Retarget below).
		var playerTiming = hudGo.Components.Create<PlayerTiming>();
		playerTiming.Target = target;

		var tuning = hudGo.Components.Create<TuningPanel>();
		tuning.Car = target;

		// Rename-a-saved-tune modal. Deliberately its OWN panel (not part of TuningPanel) so its text
		// field survives TuningPanel's every-frame rebuild — see TuneRenameState. No per-car Target: it
		// reads static rename state, so Retarget leaves it alone.
		hudGo.Components.Create<TuneRenameOverlay>();

		// Dial-explainer overlay (the "?" button on the Tuning header opens it). Also its OWN panel, for the
		// same reason as the rename modal: TuningPanel rebuilds every frame off live load, which would tear
		// down an interactive overlay placed inside it. Static content (reads only UiState.TuneHelpOpen), so
		// Retarget leaves it alone.
		hudGo.Components.Create<TuneHelpOverlay>();

		var session = hudGo.Components.Create<SessionMenu>();
		session.Target = target;

		// Public-UX panels. WorldControls (M) reads the live world/terrain state and drives the
		// bootstrap's real rebuild path; HelpOverlay (I) is static content that auto-shows once on a
		// fresh clone. Neither needs a per-car Target, so Retarget leaves them alone.
		// World switching is feature-gated off for now (players stay on Town) — only mount the panel
		// when the gate is on, so the M hotkey and panel are fully absent until the Stunt Track returns.
		if ( GameBootstrap.WorldSwitchEnabled )
			hudGo.Components.Create<WorldControls>();
		hudGo.Components.Create<HelpOverlay>();
	}

	/// <summary>
	/// Re-point every mounted HUD panel at a new controller after a live car swap
	/// (<see cref="CarSwitcher"/>). Same panel roster as <see cref="Mount"/>, so the HUD/telemetry/
	/// tuning/session all follow the car under control instead of a destroyed object.
	/// </summary>
	public static void Retarget( Scene scene, VehicleController target )
	{
		if ( scene is null || !target.IsValid() )
			return;

		foreach ( var hud in scene.GetAllComponents<VehicleHud>() )
			hud.Target = target;
		foreach ( var tel in scene.GetAllComponents<TelemetryPanel>() )
			tel.Target = target;
		foreach ( var pt in scene.GetAllComponents<PlayerTiming>() )
			pt.Target = target;
		foreach ( var tun in scene.GetAllComponents<TuningPanel>() )
			tun.Car = target;
		foreach ( var ses in scene.GetAllComponents<SessionMenu>() )
			ses.Target = target;
	}
}