Capabilities/GlinerCapabilityProbe.cs
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Sandbox;
using Sandbox.Diagnostics;

namespace GlinerPoc.Capabilities;

/// <summary>
/// Phase 0 capability probe. Read-only verification that the APIs the GLiNER
/// runtime will depend on compile and execute under the current s&box whitelist
/// (engine 26.09.15). No model inference happens here.
///
/// Evidence levels produced by this probe:
///   COMPILE+RUNTIME = API executed in play mode with expected result.
///   COMPILE-ONLY    = API compiled; execution not exercised.
///   BLOCKED         = whitelist rejected the API (see probe history in
///                     docs/ai/gliner/CAPABILITIES.md; blocked-API checks are
///                     temporary files that are removed after recording).
/// </summary>
[Title( "GLiNER Capability Probe" )]
[Category( "GLiNER" )]
public sealed class GlinerCapabilityProbe : Component
{
	[Property]
	public bool RunOnStart { get; set; } = true;

	private int _checksPassed;
	private int _checksFailed;

	protected override void OnStart()
	{
		if ( !RunOnStart )
		{
			return;
		}

		_ = RunAsync();
	}

	private async Task RunAsync()
	{
		Log.Info( "[GLI:PROBE] capability probe start" );

		// Main-thread API checks first (fail fast, deterministic).
		Check( "span", () =>
		{
			Span<float> s = stackalloc float[4];
			for ( int i = 0; i < s.Length; i++ ) s[i] = i * 0.5f;
			return s[3] == 1.5f;
		} );

		Check( "bitconverter", () =>
		{
			byte[] b = BitConverter.GetBytes( 1.0f );
			return b.Length == 4 && BitConverter.ToSingle( b, 0 ) == 1.0f;
		} );

		Check( "sha256_hashdata", () =>
		{
			byte[] h = SHA256.HashData( Encoding.UTF8.GetBytes( "abc" ) );
			return Convert.ToHexString( h ).ToLowerInvariant() ==
				"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
		} );

		Check( "mathf_fma", () =>
		{
			// 2,1,3 -> 2*1+3 = 5
			return MathF.FusedMultiplyAdd( 2f, 1f, 3f ) == 5f;
		} );

		// BLOCKED (SB1000, engine 26.09.15 - recorded in CAPABILITIES.md, probe removed):
		//   System.Numerics.Vector<T>.get_Count and .IsHardwareAccelerated
		//   System.Runtime.InteropServices.MemoryMarshal.Cast<TFrom,TTo>(ReadOnlySpan<TFrom>)
		// Managed SIMD via System.Numerics.Vector<T> is therefore unavailable under
		// the whitelist; P08 may not assume it without its own dedicated probe.

		Check( "utf8_encoding", () =>
		{
			string s = "s\u00e9\u1e57box \u4f60\u597d";
			byte[] b = Encoding.UTF8.GetBytes( s );
			return Encoding.UTF8.GetString( b ) == s && b.Length > s.Length;
		} );

		Check( "regex_unicode", () =>
		{
			var rx = new System.Text.RegularExpressions.Regex( "\\p{Lu}+" );
			return rx.Matches( "abcDEFghi" ).Count == 1;
		} );

		Check( "double_parse_invariant", () =>
		{
			return double.Parse( "1.5", System.Globalization.CultureInfo.InvariantCulture ) == 1.5;
		} );

		// Half precision: compile-allowed, but runtime behaviour is suspect
		// (passed interpreted, failed DMD-compiled on engine 26.09.15). Log the
		// actual conversions; the runtime must not depend on System.Half.
		Check( "half_convert", () =>
		{
			float a = (float)(System.Half)1.5f;
			float b = (float)(System.Half)0.5f;
			float c = (float)(System.Half)65504f;
			Log.Info( $"[GLI:PROBE] half values (Half)1.5f->{a} (Half)0.5f->{b} " +
				"(Half)65504f->" + c.ToString( "R" ) );
			return a == 1.5f && b == 0.5f && c == 65504f;
		} );

		// Worker-thread execution: the load-bearing CPU-heavy work path.
		int mainThreadId = Environment.CurrentManagedThreadId;
		int workerThreadId = -1;
		bool workerRan = false;
		await Task.RunInThreadAsync( () =>
		{
			workerThreadId = Environment.CurrentManagedThreadId;
			workerRan = true;
		} );
		Log.Info( $"[GLI:PROBE] task_runinthreadasync ran={workerRan} " +
			$"main_thread={mainThreadId} worker_thread={workerThreadId} " +
			$"off_main={workerRan && workerThreadId != mainThreadId}" );
		if ( workerRan && workerThreadId != mainThreadId ) _checksPassed++;
		else { _checksFailed++; Log.Error( "[GLI:PROBE] FAIL task_runinthreadasync" ); }

		// Session-scoped worker alternative (no component lifetime required).
		int sessionThreadId = -1;
		bool sessionRan = false;
		await GameTask.RunInThreadAsync( () =>
		{
			sessionThreadId = Environment.CurrentManagedThreadId;
			sessionRan = true;
		} );
		Log.Info( $"[GLI:PROBE] gametask_runinthreadasync ran={sessionRan} " +
			$"main_thread={mainThreadId} worker_thread={sessionThreadId} " +
			$"off_main={sessionRan && sessionThreadId != mainThreadId}" );
		if ( sessionRan && sessionThreadId != mainThreadId ) _checksPassed++;
		else { _checksFailed++; Log.Error( "[GLI:PROBE] FAIL gametask_runinthreadasync" ); }

		Log.Info( $"[GLI:PROBE] capability probe complete passed={_checksPassed} " +
			$"failed={_checksFailed}" );
	}

	private void Check( string name, Func<bool> check )
	{
		try
		{
			bool ok = check();
			if ( ok )
			{
				_checksPassed++;
				Log.Info( $"[GLI:PROBE] PASS {name}" );
			}
			else
			{
				_checksFailed++;
				Log.Error( $"[GLI:PROBE] FAIL {name}" );
			}
		}
		catch ( Exception e )
		{
			_checksFailed++;
			Log.Error( $"[GLI:PROBE] FAIL {name} exception={e.GetType().Name}: {e.Message}" );
		}
	}
}