Player/Car/AI/DriverPersonality.cs

DriverPersonality is a simple immutable data type describing AI driver traits. It holds driving parameters (lateral acceleration, top speed factor, steering, aggression, mistake rate, lateral bias, look-ahead and drift chance) and has a Create(seed,difficulty) factory that seeds the RNG and returns randomized values per difficulty.

namespace Machines.Player;

/// <summary>
/// Driver traits and personality for driving style.
/// </summary>
public sealed class DriverPersonality
{
	/// <summary>
	/// Lateral acceleration factor (0-1)
	/// </summary>
	public float LatAccel { get; init; }

	/// <summary>
	/// Top speed factor (0–1)
	/// </summary>
	public float MaxSpeed { get; init; }

	/// <summary>
	/// Steering sharpness factor (0–1)
	/// </summary>
	public float SteerSharpness { get; init; }

	/// <summary>
	/// Steering smoothing factor (0–1)
	/// </summary>
	public float SteerSmoothing { get; init; }

	/// <summary>
	/// How aggressive the bot is in overtaking and defending (0–1)
	/// </summary>
	public float Aggression { get; init; }

	/// <summary>
	/// How often the bot makes mistakes (0–1)
	/// </summary>
	public float MistakeRate { get; init; }

	/// <summary>
	/// Track-side preference: 0 = center, -1 = left, 1 = right
	/// </summary>
	public float LateralBias { get; init; }

	/// <summary>
	/// Look-ahead scale: 1 = normal, more = further, less = shorter
	/// </summary>
	public float LookAheadScale { get; init; }

	/// <summary>
	/// Chance (0–1) that the bot will drift on any given corner opportunity.
	/// </summary>
	public float DriftChance { get; init; }

	/// <summary>
	/// Creates a randomized personality for the given seed and difficulty
	/// </summary>
	public static DriverPersonality Create( int seed, BotDifficulty difficulty )
	{
		Game.SetRandomSeed( seed );
		float Rand() => Game.Random.NextSingle();
		float Between( float baseValue, float spread ) => baseValue + (Rand() - 0.5f) * 2f * spread;

		return difficulty switch
		{
			BotDifficulty.Easy => new DriverPersonality
			{
				LatAccel = Between( 0.25f, 0.033f ).Clamp( 0f, 1f ),
				MaxSpeed = Between( 0.375f, 0.0375f ).Clamp( 0f, 1f ),
				SteerSharpness = Between( 0.2f, 0.085f ).Clamp( 0f, 1f ),
				SteerSmoothing = Between( 0.333f, 0.125f ).Clamp( 0f, 1f ),
				Aggression = Between( 0.25f, 0.15f ).Clamp( 0f, 1f ),
				MistakeRate = Between( 0.15f, 0.08f ).Clamp( 0f, 1f ),
				LateralBias = Between( 0f, 0.6f ).Clamp( -1f, 1f ),
				LookAheadScale = Between( 1.0f, 0.15f ),
				DriftChance = Between( 0.2f, 0.1f ).Clamp( 0f, 1f ),
			},
			BotDifficulty.Hard => new DriverPersonality
			{
				LatAccel = Between( 1.0f, 0f ).Clamp( 0f, 1f ),
				MaxSpeed = Between( 1.0f, 0.03f ).Clamp( 0f, 1f ),
				SteerSharpness = Between( 1.0f, 0.05f ).Clamp( 0f, 1f ),
				SteerSmoothing = Between( 1.0f, 0.05f ).Clamp( 0f, 1f ),
				Aggression = Between( 0.8f, 0.15f ).Clamp( 0f, 1f ),
				MistakeRate = Between( 0.015f, 0.015f ).Clamp( 0f, 1f ),
				LateralBias = Between( 0f, 0.6f ).Clamp( -1f, 1f ),
				LookAheadScale = Between( 1.2f, 0.15f ),
				DriftChance = Between( 0.7f, 0.15f ).Clamp( 0f, 1f ),
			},
			_ => new DriverPersonality
			{
				LatAccel = Between( 0.458f, 0.05f ).Clamp( 0f, 1f ),
				MaxSpeed = Between( 0.5625f, 0.0375f ).Clamp( 0f, 1f ),
				SteerSharpness = Between( 0.43f, 0.114f ).Clamp( 0f, 1f ),
				SteerSmoothing = Between( 0.5f, 0.167f ).Clamp( 0f, 1f ),
				Aggression = Between( 0.5f, 0.2f ).Clamp( 0f, 1f ),
				MistakeRate = Between( 0.06f, 0.04f ).Clamp( 0f, 1f ),
				LateralBias = Between( 0f, 0.6f ).Clamp( -1f, 1f ),
				LookAheadScale = Between( 1.1f, 0.15f ),
				DriftChance = Between( 0.45f, 0.2f ).Clamp( 0f, 1f ),
			},
		};
	}
}