OMRVisualFoundation.Bloom.cs
using Sandbox;
using System;

/// <summary>
/// Phase 8F3F3: restrained native HDR bloom for the gameplay world.
///
/// This deliberately uses Sandbox.Bloom instead of reproducing the engine's
/// multi-resolution bloom chain in an OMR shader. The native implementation
/// thresholds the HDR scene, builds a six-level half-resolution pyramid and
/// composites the accumulated glow before later post-processing.
///
/// Bloom stays on the authored gameplay/world camera. In OMR's two-camera
/// presentation stack that means the world receives bloom while the offscreen
/// first-person viewmodel remains clean. The later adaptive pixelation/edge
/// passes can then stylize the already-bloomed world instead of Bloom blurring
/// their pixel structure afterward.
/// </summary>
public sealed partial class OMRVisualFoundation
{
    [Property, Group( "World Look/Bloom" ), Title( "Enable Bloom" )]
    [Description( "Native HDR bloom for bright world lighting and emissive/high-intensity regions. The local viewmodel remains outside this pass." )]
    public bool EnableBloom { get; set; } = false;

    [Property, Group( "World Look/Bloom" ), Title( "Mode" )]
    public SceneCamera.BloomAccessor.BloomMode BloomMode { get; set; } = SceneCamera.BloomAccessor.BloomMode.Additive;

    [Property, Group( "World Look/Bloom" ), Range( 0f, 10f ), Title( "Strength" )]
    [Description( "Overall bloom contribution. OMR starts deliberately low; high values quickly wash out competitive contrast." )]
    public float BloomStrength { get; set; } = 0.18f;

    [Property, Group( "World Look/Bloom" ), Range( 0f, 2f ), Title( "Threshold" )]
    [Description( "HDR brightness required before a pixel contributes to bloom. Higher values restrict glow to the brightest lights/highlights." )]
    public float BloomThreshold { get; set; } = 1.10f;

    [Property, Group( "World Look/Bloom" ), Title( "Tint" )]
    [Description( "Multiplies the accumulated bloom color. White preserves the authored light colors." )]
    public Color BloomTint { get; set; } = Color.White;

    [Button, Group( "World Look/Bloom" ), Title( "Subtle Standard Preset" )]
    public void ApplyBloomSubtlePreset()
    {
        EnableBloom = true;
        BloomMode = SceneCamera.BloomAccessor.BloomMode.Additive;
        BloomStrength = 0.18f;
        BloomThreshold = 1.10f;
        BloomTint = Color.White;
        RebuildBloom();
    }

    [Button, Group( "World Look/Bloom" ), Title( "Disable Bloom" )]
    public void DisableBloom()
    {
        EnableBloom = false;
        RebuildBloom();
    }

    [Button, Group( "World Look/Bloom" ), Title( "Setup Bloom Preview" )]
    [Description( "Adds/reuses Sandbox.Bloom on the gameplay camera for editor preview and runtime world bloom." )]
    public void SetupBloomPreview()
    {
        CameraComponent camera = GetComponent<CameraComponent>( true );
        if ( camera is null || !camera.IsValid )
        {
            Log.Warning(
                "[OMR VISUAL] Bloom preview setup failed: OMRVisualFoundation must share a GameObject with the gameplay CameraComponent."
            );
            return;
        }

        ResolveBloomComponent();

        if ( _bloom is null || !_bloom.IsValid )
        {
            _bloom = GameObject.AddComponent<Bloom>( false );
            _bloomCreatedByFoundation = false;
        }

        ApplyBloomSettings();

        Log.Info(
            $"[OMR VISUAL] Native Bloom ready on '{GameObject.Name}' | " +
            $"Enabled:{_bloom.Enabled} | Mode:{_bloom.Mode} | " +
            $"Strength:{_bloom.Strength:0.###} | Threshold:{_bloom.Threshold:0.###}"
        );
    }

    private Bloom _bloom;
    private bool _bloomCreatedByFoundation;
    private bool _bloomReadyLogged;

    private float EffectiveBloomStrength =>
        EnableWorldLook && EnableBloom
            ? MathX.Clamp( BloomStrength, 0f, 10f ) * MathX.Clamp( BlendWeight, 0f, 1f )
            : 0f;

    private void ResetBloomSettings()
    {
        EnableBloom = false;
        BloomMode = SceneCamera.BloomAccessor.BloomMode.Additive;
        BloomStrength = 0.18f;
        BloomThreshold = 1.10f;
        BloomTint = Color.White;
    }

    private void ResolveBloomComponent()
    {
        if ( _bloom is not null && _bloom.IsValid )
            return;

        _bloom = GetComponent<Bloom>( true );
        _bloomCreatedByFoundation = false;
    }

    private void RebuildBloom()
    {
        CameraComponent camera = GetComponent<CameraComponent>( true );
        if ( camera is null || !camera.IsValid )
            return;

        ResolveBloomComponent();

        // Do not silently author a component while ExecuteInEditor is ticking.
        // The patch already includes one, and Setup Bloom Preview is available
        // for scenes that do not.
        if ( !Game.IsPlaying )
        {
            if ( _bloom is not null && _bloom.IsValid )
                ApplyBloomSettings();

            return;
        }

        if (
            EnableBloom &&
            (_bloom is null || !_bloom.IsValid)
        )
        {
            _bloom = GameObject.AddComponent<Bloom>( false );
            _bloomCreatedByFoundation = true;
        }

        if ( _bloom is null || !_bloom.IsValid )
            return;

        ApplyBloomSettings();

        if ( _bloom.Enabled && LogSetup && !_bloomReadyLogged )
        {
            _bloomReadyLogged = true;
            Log.Info(
                $"[OMR VISUAL] Native world Bloom active on '{GameObject.Name}' | " +
                $"Mode:{_bloom.Mode} | Strength:{_bloom.Strength:0.###} | " +
                $"Threshold:{_bloom.Threshold:0.###} | " +
                $"CameraPostProcessing:{camera.EnablePostProcessing}"
            );
        }

        if ( !_bloom.Enabled )
            _bloomReadyLogged = false;
    }

    private void ApplyBloomSettings()
    {
        if ( _bloom is null || !_bloom.IsValid )
            return;

        _bloom.Mode = BloomMode;
        _bloom.Strength = EffectiveBloomStrength;
        _bloom.Threshold = MathX.Clamp( BloomThreshold, 0f, 2f );
        _bloom.Tint = BloomTint;
        _bloom.Enabled = EffectiveBloomStrength > 0.0001f;
    }

    private void ReleaseBloom()
    {
        if ( _bloom is not null && _bloom.IsValid )
        {
            _bloom.Enabled = false;

            if ( _bloomCreatedByFoundation )
                _bloom.Destroy();
        }

        _bloom = null;
        _bloomCreatedByFoundation = false;
        _bloomReadyLogged = false;
    }
}