OMRBotBrainModel.cs
using Sandbox;
using System;
using System.Collections.Generic;

/// <summary>
/// BOT 7 intent vocabulary.
///
/// The active utility controller scores these intents while the tactical and
/// weapon execution layers enforce legality. Keeping the vocabulary explicit
/// lets decisions be inspected,
/// logged and compared instead of disappearing into a chain of unrelated ifs.
/// </summary>
public enum OMRBotIntentKind
{
	None,
	Engage,
	Press,
	OpenDistance,
	TakeCover,
	Reposition,
	FlankLeft,
	FlankRight,
	HoldAmbush,
	Suppress,
	ReloadSafely,
	Search,
	Intercept,
	Disengage,
	RecoverTraversal,
	HighNoon
}

/// <summary>
/// Compact description of how much trustworthy contact the bot currently has.
/// This deliberately separates current vision from remembered/inferred evidence.
/// </summary>
public enum OMRBotContactState
{
	None,
	Reacting,
	Visual,
	VisualMemory,
	Audio,
	ThreatDirection,
	Belief
}

/// <summary>
/// Readable snapshot of the lightweight BOT 6 opponent model. This is input to
/// future decisions, never a source of hidden live player information.
/// </summary>
public sealed class OMRBotOpponentModelSnapshot
{
	public int Samples { get; internal set; }
	public float Confidence { get; internal set; }
	public float Aggression { get; internal set; } = 0.50f;
	public float Mobility { get; internal set; } = 0.50f;
	public float AirUse { get; internal set; } = 0.10f;
	public float CrouchUse { get; internal set; } = 0.10f;
	public float PreferredDistance { get; internal set; } = 0.50f;
	public float RoutePredictionConfidence { get; internal set; }
	public float RepeekTendency { get; internal set; } = 0.25f;
	public float PushTendency { get; internal set; } = 0.50f;
	public string Summary { get; internal set; } = "LEARNING";
}

/// <summary>
/// BOT 7 shared blackboard.
///
/// This object contains only information the existing bot already knows or can
/// fairly observe. BOT 7A does not use it to alter behavior yet; it is a stable
/// handoff point between perception/memory and the future utility brain.
/// </summary>
public sealed class OMRBotBlackboard
{
	public float UpdatedAt { get; internal set; }
	public OMRBotDifficulty Difficulty { get; internal set; }
	public OMRBotBehaviorMode BehaviorMode { get; internal set; }

	public Vector3 BotPosition { get; internal set; }
	public Vector3 BotVelocity { get; internal set; }
	public float OwnHealthFraction { get; internal set; } = 1f;
	public float OpponentHealthFraction { get; internal set; } = 1f;
	public bool KnowsOpponentHealth { get; internal set; }

	public OMRBotContactState ContactState { get; internal set; } = OMRBotContactState.None;
	public bool HasConfirmedSight { get; internal set; }
	public bool HasVisualMemory { get; internal set; }
	public bool HasActionableAudio { get; internal set; }
	public bool HasRecentThreat { get; internal set; }
	public bool HasOpponentBelief { get; internal set; }
	public Vector3 OpponentBeliefPosition { get; internal set; }
	public float OpponentBeliefConfidence { get; internal set; }
	public bool TargetAlive { get; internal set; }
	public string TargetName { get; internal set; } = "NONE";
	public float TargetDistance { get; internal set; }

	public Vector3 ObservedTargetPosition { get; internal set; }
	public Vector3 ObservedTargetAimPoint { get; internal set; }
	public Vector3 LastKnownPosition { get; internal set; }
	public float LastSeenAge { get; internal set; } = -1f;
	public Vector3 LastSeenVelocity { get; internal set; }
	public Vector3 LastSeenDirection { get; internal set; }

	public Vector3 LastHeardPosition { get; internal set; }
	public float LastHeardAge { get; internal set; } = -1f;
	public float LastHeardConfidence { get; internal set; }
	public string LastHeardKind { get; internal set; } = string.Empty;

	public Vector3 LastThreatDirection { get; internal set; }
	public float ThreatAge { get; internal set; } = -1f;

	public bool HasCoverPlan { get; internal set; }
	public bool HasFlankPlan { get; internal set; }
	public bool HasSearchPlan { get; internal set; }
	public bool IsSuppressing { get; internal set; }
	public float TacticalAggression { get; internal set; } = 0.50f;
	public string LegacyIntent { get; internal set; } = string.Empty;

	public OMRBotWeaponDoctrine Weapon { get; internal set; } = OMRBotWeaponDoctrine.Empty;
	public OMRBotWeaponDoctrine OpponentWeapon { get; internal set; } = OMRBotWeaponDoctrine.Empty;
	public bool KnowsOpponentWeapon { get; internal set; }
	public OMRBotMatchupContext Matchup { get; internal set; } = OMRBotMatchupContext.Neutral;
	public OMRBotRuleContext Rule { get; internal set; } = OMRBotRuleContext.Standard;
	public OMRBotOpponentModelSnapshot OpponentModel { get; } = new();

	public void Reset()
	{
		UpdatedAt = 0f;
		Difficulty = OMRBotDifficulty.Normal;
		BehaviorMode = OMRBotBehaviorMode.Stationary;
		BotPosition = Vector3.Zero;
		BotVelocity = Vector3.Zero;
		OwnHealthFraction = 1f;
		OpponentHealthFraction = 1f;
		KnowsOpponentHealth = false;
		ContactState = OMRBotContactState.None;
		HasConfirmedSight = false;
		HasVisualMemory = false;
		HasActionableAudio = false;
		HasRecentThreat = false;
		HasOpponentBelief = false;
		OpponentBeliefPosition = Vector3.Zero;
		OpponentBeliefConfidence = 0f;
		TargetAlive = false;
		TargetName = "NONE";
		TargetDistance = 0f;
		ObservedTargetPosition = Vector3.Zero;
		ObservedTargetAimPoint = Vector3.Zero;
		LastKnownPosition = Vector3.Zero;
		LastSeenAge = -1f;
		LastSeenVelocity = Vector3.Zero;
		LastSeenDirection = Vector3.Zero;
		LastHeardPosition = Vector3.Zero;
		LastHeardAge = -1f;
		LastHeardConfidence = 0f;
		LastHeardKind = string.Empty;
		LastThreatDirection = Vector3.Zero;
		ThreatAge = -1f;
		HasCoverPlan = false;
		HasFlankPlan = false;
		HasSearchPlan = false;
		IsSuppressing = false;
		TacticalAggression = 0.50f;
		LegacyIntent = string.Empty;
		Weapon = OMRBotWeaponDoctrine.Empty;
		OpponentWeapon = OMRBotWeaponDoctrine.Empty;
		KnowsOpponentWeapon = false;
		Matchup = OMRBotMatchupContext.Neutral;
		Rule = OMRBotRuleContext.Standard;
		OpponentModel.Samples = 0;
		OpponentModel.Confidence = 0f;
		OpponentModel.Aggression = 0.50f;
		OpponentModel.Mobility = 0.50f;
		OpponentModel.AirUse = 0.10f;
		OpponentModel.CrouchUse = 0.10f;
		OpponentModel.PreferredDistance = 0.50f;
		OpponentModel.RoutePredictionConfidence = 0f;
		OpponentModel.RepeekTendency = 0.25f;
		OpponentModel.PushTendency = 0.50f;
		OpponentModel.Summary = "LEARNING";
	}
}

/// <summary>
/// One utility option populated and selected by the active BOT 7D decision layer.
/// </summary>
public sealed class OMRBotDecisionCandidate
{
	public OMRBotIntentKind Intent { get; set; }
	public float Score { get; set; }
	public bool Legal { get; set; } = true;
	public string Reason { get; set; } = string.Empty;
	public Vector3 TargetPosition { get; set; }
	public bool HasTargetPosition { get; set; }
}

/// <summary>
/// Reusable per-decision scratch frame. It intentionally owns a stable list so
/// later utility evaluation can avoid allocating a new candidate collection every
/// update while remaining easy to inspect in diagnostics.
/// </summary>
public sealed class OMRBotDecisionFrame
{
	private readonly List<OMRBotDecisionCandidate> _candidates = new();
	private readonly List<OMRBotDecisionCandidate> _candidatePool = new();

	public IReadOnlyList<OMRBotDecisionCandidate> Candidates => _candidates;
	public OMRBotIntentKind SelectedIntent { get; internal set; } = OMRBotIntentKind.None;
	public float SelectedScore { get; internal set; }
	public string SelectionReason { get; internal set; } = string.Empty;

	public void Clear()
	{
		_candidates.Clear();
		SelectedIntent = OMRBotIntentKind.None;
		SelectedScore = 0f;
		SelectionReason = string.Empty;
	}

	// Returned candidates are mutable scratch data; never retain them across Clear().
	public OMRBotDecisionCandidate RentCandidate()
	{
		int index = _candidates.Count;
		if ( index >= _candidatePool.Count ) _candidatePool.Add( new OMRBotDecisionCandidate() );
		OMRBotDecisionCandidate candidate = _candidatePool[index];
		_candidates.Add( candidate );
		return candidate;
	}

}