Decompiled source of InFalsusPractice v1.0.0

BepInEx/plugins/InFalsusPractice.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using FastText;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Attributes;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppInterop.Runtime.Runtime;
using Il2CppInterop.Runtime.Startup;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Threading;
using Microsoft.CodeAnalysis;
using UnityEngine;
using _E;
using _J;
using _K;
using _b;
using _k;
using ifapp.Game;
using ifapp.Game.Data;
using ifapp.Game.Scenes;
using ifapp.Game.Scenes.Game;
using ifapp.Game.Scenes.Game.UI;
using ifapp.Game.UI.Common;
using ifapp.Game.UI.Settings;
using ifapp.NativeTime;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyTitle("In Falsus Practice")]
[assembly: AssemblyCompany("SkyFrozen")]
[assembly: AssemblyCopyright("Copyright © SkyFrozen")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: SecurityPermission((SecurityAction)8, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : System.Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(/*Could not decode attribute arguments.*/)]
	internal sealed class RefSafetyRulesAttribute : System.Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace InFalsusPractice
{
	public sealed class ButtonClicks
	{
		[field: CompilerGenerated]
		public int Captured
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		public bool Handle(int id, bool down, bool up, bool enabled, bool inside)
		{
			if (down && enabled && inside && Captured == 0)
			{
				Captured = id;
			}
			if (!up || Captured != id)
			{
				return false;
			}
			Captured = 0;
			return enabled && inside;
		}

		public void Cancel()
		{
			Captured = 0;
		}
	}
	public sealed class CalibrationOffset
	{
		[field: CompilerGenerated]
		public int Baseline
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		[field: CompilerGenerated]
		public int Applied
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		public CalibrationOffset(int milliseconds)
		{
			Baseline = (Applied = milliseconds);
		}

		public int AtSpeed(int observed, double speed)
		{
			if (observed != Applied)
			{
				Baseline = observed;
			}
			Applied = (int)Math.Clamp(Math.Round((double)Baseline * speed, (MidpointRounding)1), -2147483648.0, 2147483647.0);
			return Applied;
		}
	}
	public static class ChartMirror
	{
		public static PracticeNote Reflect(PracticeNote note)
		{
			note.Lane = 5f - note.Lane;
			note.EndLane = 5f - note.EndLane;
			if (note.Kind == PracticeNoteKind.Flick && !note.FlickMirrorInvariant)
			{
				note.RightFlick = !note.RightFlick;
			}
			return note;
		}

		public static List<PracticeNote> ForReview(List<PracticeNote> source, bool mirrored)
		{
			if (!mirrored)
			{
				return source;
			}
			List<PracticeNote> val = new List<PracticeNote>(source.Count);
			for (int i = 0; i < source.Count; i++)
			{
				val.Add(Reflect(source[i]));
			}
			return val;
		}
	}
	[DefaultMember("Item")]
	public sealed class MirroredNotes : System.Collections.Generic.IReadOnlyList<PracticeNote>, System.Collections.Generic.IReadOnlyCollection<PracticeNote>, System.Collections.Generic.IEnumerable<PracticeNote>, System.Collections.IEnumerable
	{
		[CompilerGenerated]
		private sealed class <GetEnumerator>d__6 : System.Collections.Generic.IEnumerator<PracticeNote>, System.IDisposable, System.Collections.IEnumerator
		{
			private int <>1__state;

			private PracticeNote <>2__current;

			public MirroredNotes <>4__this;

			private int <i>5__2;

			PracticeNote System.Collections.Generic.IEnumerator<PracticeNote>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object System.Collections.IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <GetEnumerator>d__6(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void System.IDisposable.Dispose()
			{
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				MirroredNotes mirroredNotes = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<i>5__2 = 0;
					break;
				case 1:
					<>1__state = -1;
					<i>5__2++;
					break;
				}
				if (<i>5__2 < mirroredNotes.Count)
				{
					<>2__current = mirroredNotes[<i>5__2];
					<>1__state = 1;
					return true;
				}
				return false;
			}

			bool System.Collections.IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void System.Collections.IEnumerator.Reset()
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				throw new NotSupportedException();
			}
		}

		private readonly System.Collections.Generic.IReadOnlyList<PracticeNote> _source;

		public int Count => ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)_source).Count;

		public PracticeNote this[int index] => ChartMirror.Reflect(_source[index]);

		public MirroredNotes(System.Collections.Generic.IReadOnlyList<PracticeNote> source)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			_source = source ?? throw new ArgumentNullException("source");
		}

		[IteratorStateMachine(typeof(<GetEnumerator>d__6))]
		public System.Collections.Generic.IEnumerator<PracticeNote> GetEnumerator()
		{
			for (int i = 0; i < Count; i++)
			{
				yield return this[i];
			}
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return (System.Collections.IEnumerator)GetEnumerator();
		}
	}
	public sealed class MirrorPresentation
	{
		private System.Collections.Generic.IReadOnlyList<PracticeNote> _source;

		private System.Collections.Generic.IReadOnlyList<PracticeNote> _view;

		[field: CompilerGenerated]
		public bool Known
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		[field: CompilerGenerated]
		public bool Mirrored
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		public void Reset()
		{
			bool known = (Mirrored = false);
			Known = known;
			_source = (_view = null);
		}

		public void Resolve(bool mirrored)
		{
			if (!Known)
			{
				Known = true;
				Mirrored = mirrored;
			}
		}

		public System.Collections.Generic.IReadOnlyList<PracticeNote> Preview(System.Collections.Generic.IReadOnlyList<PracticeNote> source)
		{
			if (!Known || source == null || ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)source).Count == 0)
			{
				return System.Array.Empty<PracticeNote>();
			}
			if (!Mirrored)
			{
				return source;
			}
			if (_source != source)
			{
				_source = source;
				_view = new MirroredNotes(source);
			}
			return _view;
		}
	}
	public static class ChartOverview
	{
		[CompilerGenerated]
		private sealed class <Read>d__3 : System.Collections.Generic.IEnumerable<PracticeNote>, System.Collections.IEnumerable, System.Collections.Generic.IEnumerator<PracticeNote>, System.IDisposable, System.Collections.IEnumerator
		{
			private int <>1__state;

			private PracticeNote <>2__current;

			private int <>l__initialThreadId;

			private LogicalNotePlayer player;

			public LogicalNotePlayer <>3__player;

			private List<_fA> <source>5__2;

			private int <count>5__3;

			private int <i>5__4;

			PracticeNote System.Collections.Generic.IEnumerator<PracticeNote>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object System.Collections.IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <Read>d__3(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void System.IDisposable.Dispose()
			{
			}

			private bool MoveNext()
			{
				//IL_0088: Unknown result type (might be due to invalid IL or missing references)
				//IL_008d: Unknown result type (might be due to invalid IL or missing references)
				//IL_008e: Unknown result type (might be due to invalid IL or missing references)
				//IL_008f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0095: Expected I4, but got Unknown
				//IL_0095: Unknown result type (might be due to invalid IL or missing references)
				//IL_0096: Unknown result type (might be due to invalid IL or missing references)
				//IL_009d: Expected I4, but got Unknown
				//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
				//IL_0111: Unknown result type (might be due to invalid IL or missing references)
				//IL_0112: Unknown result type (might be due to invalid IL or missing references)
				//IL_0104: Unknown result type (might be due to invalid IL or missing references)
				//IL_0105: Unknown result type (might be due to invalid IL or missing references)
				//IL_0130: Unknown result type (might be due to invalid IL or missing references)
				//IL_0131: Unknown result type (might be due to invalid IL or missing references)
				//IL_0123: Unknown result type (might be due to invalid IL or missing references)
				//IL_0124: Unknown result type (might be due to invalid IL or missing references)
				//IL_013e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0153: Unknown result type (might be due to invalid IL or missing references)
				//IL_017b: Unknown result type (might be due to invalid IL or missing references)
				//IL_017c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0186: Expected I4, but got Unknown
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_019b;
				}
				<>1__state = -1;
				if ((Object)(object)player == (Object)null)
				{
					return false;
				}
				<source>5__2 = player._Gb()?.Item2;
				if (<source>5__2 == null || <source>5__2.Count == 0)
				{
					return false;
				}
				<count>5__3 = <source>5__2.Count;
				<i>5__4 = 0;
				goto IL_01ad;
				IL_01ad:
				if (<i>5__4 < <count>5__3)
				{
					_fA val = <source>5__2[<i>5__4];
					int num2 = (int)val._Ae;
					int num3 = (int)val._be;
					if (num2 >= 1 && num2 <= 4 && (num3 == 1 || num3 == 2 || num3 == 4 || num3 == 5))
					{
						float x = ((num2 == 1) ? ((float)val._Ce._KD) : Rational(val._Ce));
						float endX = ((num2 == 1) ? ((float)val._de._KD) : Rational(val._de));
						float width = ((num2 == 1) ? ((float)val._De._KD) : Rational(val._De));
						float endWidth = ((num2 == 1) ? ((float)val._ee._KD) : Rational(val._ee));
						double num4 = (double)val._Be * 0.001;
						double end = Math.Max(num4, (double)val._ce * 0.001);
						<>2__current = ChartProjection.Map(num2, num3, num4, end, x, endX, width, endWidth, (int)val._he);
						<>1__state = 1;
						return true;
					}
					goto IL_019b;
				}
				return false;
				IL_019b:
				<i>5__4++;
				goto IL_01ad;
			}

			bool System.Collections.IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void System.Collections.IEnumerator.Reset()
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			System.Collections.Generic.IEnumerator<PracticeNote> System.Collections.Generic.IEnumerable<PracticeNote>.GetEnumerator()
			{
				<Read>d__3 <Read>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<Read>d__ = this;
				}
				else
				{
					<Read>d__ = new <Read>d__3(0);
				}
				<Read>d__.player = <>3__player;
				return <Read>d__;
			}

			[DebuggerHidden]
			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return (System.Collections.IEnumerator)((System.Collections.Generic.IEnumerable<PracticeNote>)this).GetEnumerator();
			}
		}

		private static readonly Dictionary<string, string> Titles = new Dictionary<string, string>((IEqualityComparer<string>)(object)StringComparer.OrdinalIgnoreCase);

		private static bool _titlesLoaded;

		public static int SourceCount(LogicalNotePlayer player)
		{
			return ((player == null) ? ((int?)null) : player._Gb()?.Item2?.Count).GetValueOrDefault();
		}

		[IteratorStateMachine(typeof(<Read>d__3))]
		public static System.Collections.Generic.IEnumerable<PracticeNote> Read(LogicalNotePlayer player)
		{
			if ((Object)(object)player == (Object)null)
			{
				yield break;
			}
			List<_fA> source = player._Gb()?.Item2;
			if (source == null || source.Count == 0)
			{
				yield break;
			}
			int count = source.Count;
			for (int i = 0; i < count; i++)
			{
				_fA val = source[i];
				int num = (int)val._Ae;
				int num2 = (int)val._be;
				if (num >= 1 && num <= 4 && (num2 == 1 || num2 == 2 || num2 == 4 || num2 == 5))
				{
					float x = ((num == 1) ? ((float)val._Ce._KD) : Rational(val._Ce));
					float endX = ((num == 1) ? ((float)val._de._KD) : Rational(val._de));
					float width = ((num == 1) ? ((float)val._De._KD) : Rational(val._De));
					float endWidth = ((num == 1) ? ((float)val._ee._KD) : Rational(val._ee));
					double num3 = (double)val._Be * 0.001;
					double end = Math.Max(num3, (double)val._ce * 0.001);
					yield return ChartProjection.Map(num, num2, num3, end, x, endX, width, endWidth, (int)val._he);
				}
			}
		}

		public static string ChartKey(GameScene scene)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected I4, but got Unknown
			if ((Object)(object)scene == (Object)null)
			{
				return "";
			}
			_nG dN = GameScene._dN;
			SongInfo val = ((dN != null) ? dN._LYA : null);
			if (val == null)
			{
				return "";
			}
			return val.BaseName + ":" + (int)dN._mYA;
		}

		public static string Title(GameScene scene)
		{
			object obj;
			if (!((Object)(object)scene == (Object)null))
			{
				_nG dN = GameScene._dN;
				if (dN == null)
				{
					obj = null;
				}
				else
				{
					SongInfo lYA = dN._LYA;
					obj = ((lYA != null) ? lYA.BaseName : null);
				}
				if (obj == null)
				{
					obj = "";
				}
			}
			else
			{
				obj = "";
			}
			string text = (string)obj;
			LoadTitles();
			string result = default(string);
			if (!Titles.TryGetValue(text, ref result))
			{
				return text;
			}
			return result;
		}

		private static float Rational(_BA value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			float num = ((value._lD != 0) ? ((float)value._KD / (float)value._lD) : value._LD);
			if (!float.IsNaN(num) && !float.IsInfinity(num))
			{
				return num;
			}
			return 0f;
		}

		private unsafe static void LoadTitles()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (_titlesLoaded)
			{
				return;
			}
			_titlesLoaded = true;
			string text = Path.Combine(Paths.ConfigPath, "InFalsusRating", "song-localization.json");
			if (!File.Exists(text))
			{
				return;
			}
			try
			{
				JsonDocument val = JsonDocument.Parse(File.ReadAllText(text), default(JsonDocumentOptions));
				try
				{
					JsonElement rootElement = val.RootElement;
					JsonElement val2 = default(JsonElement);
					if (!((JsonElement)(ref rootElement)).TryGetProperty("songs", ref val2))
					{
						return;
					}
					ArrayEnumerator val3 = ((JsonElement)(ref val2)).EnumerateArray();
					ArrayEnumerator enumerator = ((ArrayEnumerator)(ref val3)).GetEnumerator();
					try
					{
						JsonElement val4 = default(JsonElement);
						JsonElement val5 = default(JsonElement);
						JsonElement val6 = default(JsonElement);
						while (((ArrayEnumerator)(ref enumerator)).MoveNext())
						{
							JsonElement current = ((ArrayEnumerator)(ref enumerator)).Current;
							if (((JsonElement)(ref current)).TryGetProperty("base_name", ref val4) && ((JsonElement)(ref current)).TryGetProperty("titles", ref val5) && (((JsonElement)(ref val5)).TryGetProperty("ja", ref val6) || ((JsonElement)(ref val5)).TryGetProperty("en", ref val6)))
							{
								string text2 = ((JsonElement)(ref val4)).GetString();
								string text3 = ((JsonElement)(ref val6)).GetString();
								if (!string.IsNullOrEmpty(text2) && !string.IsNullOrWhiteSpace(text3))
								{
									Titles[text2] = text3;
								}
							}
						}
					}
					finally
					{
						((System.IDisposable)(*(ArrayEnumerator*)(&enumerator))/*cast due to .constrained prefix*/).Dispose();
					}
				}
				finally
				{
					((System.IDisposable)val)?.Dispose();
				}
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			catch (JsonException)
			{
			}
		}
	}
	public static class ChartProjection
	{
		public static PracticeNote Map(int side, int type, double start, double end, float x, float endX, float width, float endWidth, int nativeFlags = 0)
		{
			PracticeNote practiceNote = new PracticeNote();
			practiceNote.Start = start;
			practiceNote.End = Math.Max(start, end);
			practiceNote.Kind = type switch
			{
				2 => PracticeNoteKind.Hold, 
				5 => PracticeNoteKind.Sky, 
				4 => PracticeNoteKind.Flick, 
				_ => PracticeNoteKind.Tap, 
			};
			PracticeNote result = practiceNote;
			result.RightFlick = type == 4 && (nativeFlags & 0x400) != 0;
			int num = nativeFlags & 0x1400;
			result.FlickMirrorInvariant = type == 4 && (num == 0 || num == 5120);
			switch (side)
			{
			case 1:
				result.Surface = PracticeNoteSurface.Floor;
				result.Width = Clamp(width, 1f, 4f);
				result.EndWidth = result.Width;
				result.Lane = Clamp(x, 1f, 5f - result.Width) + (result.Width - 1f) * 0.5f;
				result.EndLane = result.Lane;
				break;
			case 2:
			case 3:
			{
				result.Surface = PracticeNoteSurface.Side;
				result.Lane = (result.EndLane = ((side != 2) ? 5 : 0));
				float width2 = (result.EndWidth = 1f);
				result.Width = width2;
				break;
			}
			default:
				result.Surface = PracticeNoteSurface.Air;
				result.Lane = 0.5f + 4f * Clamp(x, 0f, 1f);
				result.EndLane = 0.5f + 4f * Clamp(endX, 0f, 1f);
				result.Width = Clamp(4f * width, 0.1f, 4f);
				result.EndWidth = Clamp(4f * endWidth, 0.1f, 4f);
				break;
			}
			if (type == 1 || type == 4)
			{
				result.End = result.Start;
				result.EndLane = result.Lane;
				result.EndWidth = result.Width;
			}
			return result;
		}

		private static float Clamp(float value, float min, float max)
		{
			if (!float.IsFinite(value))
			{
				return min;
			}
			return Math.Clamp(value, min, max);
		}
	}
	public enum CoachingGrade : byte
	{
		Exact,
		SmallExact,
		Near,
		Break,
		Unknown
	}
	[Flags]
	public enum CoachingFlags : byte
	{
		None = 0,
		Practice = 1,
		Seek = 2,
		Loop = 4,
		PausedBoundary = 8,
		Unmatched = 0x10
	}
	public sealed class CoachingEvent
	{
		public int NoteIndex = -1;

		public int SegmentId;

		public int TimingDirection;

		public double ChartTime;

		public double OffsetMs;

		public double Rate = 1.0;

		public bool TimingValid;

		public CoachingGrade Grade;

		public CoachingFlags Flags;
	}
	public sealed class CoachingRun
	{
		public string SongKey = "";

		public string SongTitle = "";

		public string CaptureNote = "";

		public double Duration;

		public bool Completed;

		public bool PracticeUsed;

		public bool SeekUsed;

		public bool SignKnown;

		public bool CaptureIncomplete;

		public int ExcludedCaptureNotes;

		public int MissingJudgementRecords;

		public List<PracticeNote> Notes = new List<PracticeNote>();

		public List<CoachingEvent> Events = new List<CoachingEvent>();
	}
	public struct CoachingGradeCounts
	{
		public int SmallExact;

		public int Near;

		public int Break;

		public void Add(CoachingGrade grade)
		{
			switch (grade)
			{
			case CoachingGrade.SmallExact:
				SmallExact++;
				break;
			case CoachingGrade.Near:
				Near++;
				break;
			case CoachingGrade.Break:
				Break++;
				break;
			}
		}
	}
	public struct CoachingTypeCounts
	{
		public CoachingGradeCounts Tap;

		public CoachingGradeCounts Hold;

		public CoachingGradeCounts Field;

		public CoachingGradeCounts Flick;

		public void Add(PracticeNoteKind kind, CoachingGrade grade)
		{
			switch (kind)
			{
			case PracticeNoteKind.Tap:
				Tap.Add(grade);
				break;
			case PracticeNoteKind.Hold:
				Hold.Add(grade);
				break;
			case PracticeNoteKind.Sky:
				Field.Add(grade);
				break;
			case PracticeNoteKind.Flick:
				Flick.Add(grade);
				break;
			}
		}
	}
	public sealed class CoachingRecommendation
	{
		public string PatternKey = "";

		public string Label = "";

		public string Evidence = "";

		public string Advice = "";

		public string AnalysisDetail = "";

		public double Start;

		public double End;

		public double Priority;

		public double HistoryWeight = 1.0;

		public int SmallExact;

		public int Near;

		public int Break;

		public int HistorySongs;

		public int Occurrences;

		public double CoreStart;

		public double CoreEnd;

		public int CoreNotes;

		public int CoreExpectedNotes;

		public int SimilarSmallExact;

		public int SimilarNear;

		public int SimilarBreak;

		public CoachingTypeCounts NoteOutcomes;

		public CoachingPatternDetail Configuration;

		internal int SourceOffset;

		internal int SourceCount;

		internal string ViewCompact;
	}
	public sealed class CoachingReport
	{
		public string SongTitle = "";

		public string Summary = "";

		public string TimingTitle = "";

		public string TimingEvidence = "";

		public string TimingDisplay = "";

		public string ExcludedReason = "";

		public string TransientEvidence = "";

		public double OffsetMs;

		public double SpreadMs;

		public double TimingConfidence;

		public double SuspectedTransientRate;

		public double DriftMs;

		public double CaptureCoverage;

		public int TimingSamples;

		public int ObservedNotes;

		public int UnmatchedEvents;

		public int DuplicateEvents;

		public int TransientEvents;

		public int HistoryPatterns;

		public int EarlyCount;

		public int LateCount;

		public int InterruptedEvents;

		public int ExcludedCaptureNotes;

		public int MissingJudgementRecords;

		public bool HasOffsetEstimate;

		public bool HasDirectionEstimate;

		public bool Learned;

		public bool PracticeExcluded;

		public bool SystematicBiasCandidate;

		public List<CoachingRecommendation> Recommendations = new List<CoachingRecommendation>();

		public CoachingTypeCounts NoteOutcomes;
	}
	public sealed class CoachingMemoryEntry
	{
		public string Key = "";

		public string Label = "";

		public string LastSong = "";

		public double ErrorEwma;

		public double Support;

		public double LastStart;

		public double LastEnd;

		public int Songs;

		public int Occurrences;

		public long UpdatedUtcTicks;
	}
	public sealed class CoachingMemory
	{
		public int Version = 1;

		public List<CoachingMemoryEntry> Patterns = new List<CoachingMemoryEntry>();
	}
	public static class CoachingAnalysis
	{
		private sealed class Sample
		{
			public CoachingEvent Event;

			public Phrase Phrase;

			public double Weight;

			public double Factor = 1.0;
		}

		private sealed class Phrase
		{
			public string Key;

			public string Label;

			public double Start;

			public double End;

			public double Weight;

			public double RawWeight;

			public double Exposure;

			public int Observed;

			public int Expected;

			public int Small;

			public int Near;

			public int Break;

			public int OrderOffset;

			public CoachingTypeCounts NoteOutcomes;
		}

		private sealed class Aggregate
		{
			public string Key;

			public string Label;

			public double Weight;

			public double Exposure;

			public double LearnWeight;

			public double LearnExposure;

			public int Occurrences;

			public int Small;

			public int Near;

			public int Break;

			public int FailingOccurrences;

			public int LearnOccurrences;

			public int LearnNear;

			public int LearnBreak;

			public Phrase Worst;

			public Phrase LearnWorst;
		}

		public const int MaximumNotes = 30000;

		public const int MaximumPatterns = 256;

		private const double SmallWeight = 0.35;

		private const double NearWeight = 1.0;

		private const double BreakWeight = 2.2;

		public static CoachingReport Analyze(CoachingRun run, CoachingMemory memory = null, bool learn = true)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0802: Unknown result type (might be due to invalid IL or missing references)
			//IL_0807: Unknown result type (might be due to invalid IL or missing references)
			//IL_0861: Unknown result type (might be due to invalid IL or missing references)
			//IL_0866: Unknown result type (might be due to invalid IL or missing references)
			//IL_0efc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f01: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f1b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f20: Unknown result type (might be due to invalid IL or missing references)
			//IL_10e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_10e6: Unknown result type (might be due to invalid IL or missing references)
			if (run == null)
			{
				throw new ArgumentNullException("run");
			}
			CoachingReport coachingReport = new CoachingReport
			{
				SongTitle = (run.SongTitle ?? ""),
				ExcludedCaptureNotes = run.ExcludedCaptureNotes,
				MissingJudgementRecords = run.MissingJudgementRecords
			};
			bool flag = run.PracticeUsed || run.SeekUsed;
			Phrase[] byNote;
			List<int> order;
			List<Phrase> val = BuildPhrases(run.Notes ?? new List<PracticeNote>(), out byNote, out order);
			List<Sample> val2 = new List<Sample>();
			HashSet<int> val3 = new HashSet<int>();
			Enumerator<CoachingEvent> enumerator = (run.Events ?? new List<CoachingEvent>()).GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					CoachingEvent current = enumerator.Current;
					if (current != null)
					{
						if ((current.Flags & CoachingFlags.PausedBoundary) != CoachingFlags.None)
						{
							coachingReport.InterruptedEvents++;
						}
						if ((current.Flags & (CoachingFlags.Practice | CoachingFlags.Seek | CoachingFlags.Loop)) != CoachingFlags.None || !double.IsFinite(current.Rate) || Math.Abs(current.Rate - 1.0) > 0.001)
						{
							flag = true;
						}
						if (current.NoteIndex < 0 || current.NoteIndex >= byNote.Length || byNote[current.NoteIndex] == null || current.Grade == CoachingGrade.Unknown || !double.IsFinite(current.ChartTime) || (current.Flags & CoachingFlags.Unmatched) != CoachingFlags.None)
						{
							coachingReport.UnmatchedEvents++;
						}
						else if (!val3.Add(current.NoteIndex))
						{
							coachingReport.DuplicateEvents++;
						}
						else if (current.Flags == CoachingFlags.None && !(Math.Abs(current.Rate - 1.0) > 0.001))
						{
							val2.Add(new Sample
							{
								Event = current,
								Phrase = byNote[current.NoteIndex],
								Weight = Weight(current.Grade)
							});
						}
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			val2.Sort((Comparison<Sample>)((Sample a, Sample b) => a.Event.ChartTime.CompareTo(b.Event.ChartTime)));
			coachingReport.ObservedNotes = val2.Count;
			coachingReport.PracticeExcluded = flag;
			int num = 0;
			Enumerator<Phrase> enumerator2 = val.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					Phrase current2 = enumerator2.Current;
					num += current2.Expected;
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
			coachingReport.CaptureCoverage = ((num == 0) ? 0.0 : Math.Min(1.0, (double)val2.Count / (double)num));
			DetectTransient(val2, coachingReport);
			Enumerator<Sample> enumerator3 = val2.GetEnumerator();
			try
			{
				while (enumerator3.MoveNext())
				{
					Sample current3 = enumerator3.Current;
					Phrase phrase = current3.Phrase;
					phrase.Observed++;
					phrase.Exposure += 1.0;
					phrase.Weight += current3.Weight * current3.Factor;
					phrase.RawWeight += current3.Weight;
					PracticeNoteKind kind = run.Notes[current3.Event.NoteIndex].Kind;
					phrase.NoteOutcomes.Add(kind, current3.Event.Grade);
					coachingReport.NoteOutcomes.Add(kind, current3.Event.Grade);
					if (current3.Event.Grade == CoachingGrade.SmallExact)
					{
						phrase.Small++;
					}
					if (current3.Event.Grade == CoachingGrade.Near)
					{
						phrase.Near++;
					}
					if (current3.Event.Grade == CoachingGrade.Break)
					{
						phrase.Break++;
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose();
			}
			Dictionary<string, Aggregate> val4 = new Dictionary<string, Aggregate>((IEqualityComparer<string>)(object)StringComparer.Ordinal);
			enumerator2 = val.GetEnumerator();
			try
			{
				Aggregate aggregate = default(Aggregate);
				while (enumerator2.MoveNext())
				{
					Phrase current4 = enumerator2.Current;
					if (current4.Observed < 3 || (double)current4.Observed < (double)current4.Expected * 0.8)
					{
						continue;
					}
					if (!val4.TryGetValue(current4.Key, ref aggregate))
					{
						aggregate = new Aggregate
						{
							Key = current4.Key,
							Label = current4.Label
						};
						val4.Add(current4.Key, aggregate);
					}
					aggregate.Weight += current4.Weight;
					aggregate.Exposure += current4.Exposure;
					aggregate.Occurrences++;
					aggregate.Small += current4.Small;
					aggregate.Near += current4.Near;
					aggregate.Break += current4.Break;
					if (current4.Weight >= 1.0)
					{
						aggregate.FailingOccurrences++;
					}
					if (aggregate.Worst == null || current4.Weight / Math.Max(1.0, current4.Exposure) > aggregate.Worst.Weight / Math.Max(1.0, aggregate.Worst.Exposure))
					{
						aggregate.Worst = current4;
					}
					if (current4.Observed == current4.Expected)
					{
						aggregate.LearnWeight += current4.Weight;
						aggregate.LearnExposure += current4.Exposure;
						aggregate.LearnOccurrences++;
						aggregate.LearnNear += current4.Near;
						aggregate.LearnBreak += current4.Break;
						if (aggregate.LearnWorst == null || current4.Weight / Math.Max(1.0, current4.Exposure) > aggregate.LearnWorst.Weight / Math.Max(1.0, aggregate.LearnWorst.Exposure))
						{
							aggregate.LearnWorst = current4;
						}
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
			AnalyzeTiming(run, val2, flag, coachingReport);
			if (coachingReport.CaptureCoverage < 0.65)
			{
				coachingReport.HasOffsetEstimate = (coachingReport.HasDirectionEstimate = (coachingReport.SystematicBiasCandidate = false));
				coachingReport.TimingTitle = "延迟:有效覆盖不足";
				coachingReport.TimingEvidence = $"有效覆盖 {coachingReport.CaptureCoverage:0%};排除中断或缺失数据后,剩余片段不足以代表整曲。";
				if (coachingReport.ObservedNotes == 0 && !string.IsNullOrWhiteSpace(run.CaptureNote))
				{
					coachingReport.TimingTitle = "延迟:尚无有效判定";
					coachingReport.TimingEvidence = "采集原因:" + Limit(run.CaptureNote, 100);
				}
			}
			coachingReport.TimingDisplay = coachingReport.TimingEvidence;
			if (run.Completed && run.CaptureIncomplete && coachingReport.ObservedNotes > 0)
			{
				coachingReport.TimingEvidence = $"有效采集 {coachingReport.ObservedNotes}/{num} 音符({coachingReport.CaptureCoverage:0.0%});已排除 {run.ExcludedCaptureNotes} 个未完整采集音符。" + coachingReport.TimingEvidence;
			}
			coachingReport.TimingConfidence *= coachingReport.CaptureCoverage;
			Dictionary<string, CoachingMemoryEntry> val5 = MemoryMap(memory);
			double num2 = 0.0;
			double num3 = 0.0;
			Enumerator<string, Aggregate> enumerator4 = val4.Values.GetEnumerator();
			try
			{
				while (enumerator4.MoveNext())
				{
					Aggregate current5 = enumerator4.Current;
					num2 += current5.Weight;
					num3 += current5.Exposure;
				}
			}
			finally
			{
				((System.IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose();
			}
			double num4 = num2 / Math.Max(1.0, num3);
			enumerator4 = val4.Values.GetEnumerator();
			try
			{
				CoachingMemoryEntry coachingMemoryEntry = default(CoachingMemoryEntry);
				while (enumerator4.MoveNext())
				{
					Aggregate current6 = enumerator4.Current;
					if (!(current6.Weight <= 0.1))
					{
						bool flag2 = current6.Near == 0 && current6.Break == 0;
						if (!(coachingReport.SystematicBiasCandidate && flag2) || !(current6.Weight / current6.Exposure <= num4 * 1.25))
						{
							val5.TryGetValue(current6.Key, ref coachingMemoryEntry);
							double num5 = ((coachingMemoryEntry == null) ? 0.0 : (Math.Min(1.0, coachingMemoryEntry.Support / 48.0) * Math.Min(1.0, (double)coachingMemoryEntry.Songs / 3.0)));
							double num6 = 1.0 + ((coachingMemoryEntry == null) ? 0.0 : ((coachingMemoryEntry.ErrorEwma - 0.16) * 1.7 * num5));
							num6 = Math.Clamp(num6, 0.75, 1.55);
							double num7 = 1.0 + Math.Min(0.6, (double)Math.Max(0, current6.FailingOccurrences - 1) * 0.15);
							double num8 = current6.Weight / Math.Max(1.0, current6.Exposure) / 2.2;
							double num9 = Math.Min(1.0, current6.Exposure / 16.0);
							Phrase worst = current6.Worst;
							CoachingRecommendation coachingRecommendation = new CoachingRecommendation();
							coachingRecommendation.PatternKey = current6.Key;
							coachingRecommendation.Label = current6.Label;
							coachingRecommendation.Start = Math.Max(0.0, worst.Start - 0.6);
							coachingRecommendation.End = Math.Min(Math.Max(run.Duration, worst.End), worst.End + 0.6);
							coachingRecommendation.Priority = Math.Clamp(100.0 * num8 * num7 * (0.5 + 0.5 * num9) * num6, 0.0, 100.0);
							coachingRecommendation.HistoryWeight = num6;
							coachingRecommendation.SmallExact = worst.Small;
							coachingRecommendation.Near = worst.Near;
							coachingRecommendation.Break = worst.Break;
							coachingRecommendation.HistorySongs = coachingMemoryEntry?.Songs ?? 0;
							coachingRecommendation.Occurrences = current6.Occurrences;
							coachingRecommendation.CoreStart = worst.Start;
							coachingRecommendation.CoreEnd = worst.End;
							coachingRecommendation.CoreNotes = worst.Observed;
							coachingRecommendation.CoreExpectedNotes = worst.Expected;
							coachingRecommendation.NoteOutcomes = worst.NoteOutcomes;
							coachingRecommendation.SimilarSmallExact = current6.Small;
							coachingRecommendation.SimilarNear = current6.Near;
							coachingRecommendation.SimilarBreak = current6.Break;
							coachingRecommendation.SourceOffset = worst.OrderOffset;
							coachingRecommendation.SourceCount = worst.Expected;
							coachingRecommendation.Advice = PracticeAdvice(worst.NoteOutcomes, worst.Near, worst.Break);
							coachingRecommendation.AnalysisDetail = string.Concat(new string[10]
							{
								$"本核心段有效判定:小 E {worst.Small} · Near {worst.Near} · Break {worst.Break}。",
								TypeEvidence(worst.NoteOutcomes),
								(run.Completed && !flag && coachingReport.CaptureCoverage == 1.0) ? "全曲 Flick 按音符计:" : "有效样本 Flick 按音符计:",
								Counts(coachingReport.NoteOutcomes.Flick),
								"。",
								$"同类 {current6.Occurrences} 段合计:小 E {current6.Small} · Near {current6.Near} · Break {current6.Break};明显失误 {current6.FailingOccurrences} 段。",
								(current6.FailingOccurrences >= 2) ? "多段复现,优先练习。" : ((current6.FailingOccurrences == 1) ? "单段证据,暂不认定为稳定弱项。" : "目前仅轻微失误,暂不认定为稳定弱项。"),
								(coachingMemoryEntry != null) ? $"历史 {coachingMemoryEntry.Songs} 次,权重 {num6:0.00}×。" : "尚无历史对照。",
								" 判定权重 0.35 / 1 / 2.2,短暂异常另行降权。",
								$"核心段 {worst.Start:0.0}–{worst.End:0.0} 秒,采集 {worst.Observed}/{worst.Expected} 个音符;建议区间含前后缓冲,缓冲音符不计入核心段。配置名描述组合,不等于该类型出错;长条取单音符最差判定,不等于官方持续判定总数。"
							});
							CoachingRecommendation coachingRecommendation2 = coachingRecommendation;
							coachingRecommendation2.Evidence = RecommendationText(coachingRecommendation2);
							coachingReport.Recommendations.Add(coachingRecommendation2);
						}
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose();
			}
			coachingReport.Recommendations.Sort((Comparison<CoachingRecommendation>)((CoachingRecommendation a, CoachingRecommendation b) => b.Priority.CompareTo(a.Priority)));
			if (coachingReport.Recommendations.Count > 5)
			{
				coachingReport.Recommendations.RemoveRange(5, coachingReport.Recommendations.Count - 5);
			}
			Enumerator<CoachingRecommendation> enumerator5 = coachingReport.Recommendations.GetEnumerator();
			try
			{
				while (enumerator5.MoveNext())
				{
					CoachingRecommendation current7 = enumerator5.Current;
					List<CoachingEvent> val6 = new List<CoachingEvent>(8);
					enumerator3 = val2.GetEnumerator();
					try
					{
						while (enumerator3.MoveNext())
						{
							Sample current8 = enumerator3.Current;
							if (current8.Phrase.OrderOffset == current7.SourceOffset && current8.Event.Grade != CoachingGrade.Exact)
							{
								val6.Add(current8.Event);
							}
						}
					}
					finally
					{
						((System.IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose();
					}
					CoachingPatternDetail coachingPatternDetail = CoachingPatterns.Describe((System.Collections.Generic.IReadOnlyList<PracticeNote>)run.Notes, (System.Collections.Generic.IReadOnlyList<int>)order, current7.SourceOffset, current7.SourceCount, (System.Collections.Generic.IReadOnlyList<CoachingEvent>)val6);
					if (coachingPatternDetail.Name.Length > 0)
					{
						current7.Configuration = coachingPatternDetail;
						current7.Start = Math.Min(current7.Start, Math.Max(0.0, coachingPatternDetail.ContextStart - 0.1));
						current7.End = Math.Max(current7.End, Math.Min(Math.Max(run.Duration, coachingPatternDetail.ContextEnd), coachingPatternDetail.ContextEnd + 0.1));
						int num10 = current7.Label.LastIndexOf(" · ", (StringComparison)4);
						current7.Label = coachingPatternDetail.Name + ((num10 >= 0) ? current7.Label.Substring(num10) : "");
						current7.Advice = coachingPatternDetail.Drill;
						current7.Evidence = RecommendationText(current7);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator5/*cast due to .constrained prefix*/).Dispose();
			}
			if (learn && memory != null && run.Completed && !flag && val2.Count >= 24 && coachingReport.CaptureCoverage >= 0.85)
			{
				int num11 = 0;
				enumerator4 = val4.Values.GetEnumerator();
				try
				{
					CoachingMemoryEntry coachingMemoryEntry2 = default(CoachingMemoryEntry);
					while (enumerator4.MoveNext())
					{
						Aggregate current9 = enumerator4.Current;
						if (!(current9.LearnExposure < 8.0))
						{
							if (!val5.TryGetValue(current9.Key, ref coachingMemoryEntry2))
							{
								coachingMemoryEntry2 = new CoachingMemoryEntry
								{
									Key = current9.Key,
									Label = current9.Label
								};
								val5.Add(current9.Key, coachingMemoryEntry2);
							}
							double num12 = ((coachingReport.SystematicBiasCandidate && current9.LearnNear == 0 && current9.LearnBreak == 0) ? 0.35 : 1.0);
							double num13 = Math.Clamp(current9.LearnWeight / current9.LearnExposure / 2.2 * num12, 0.0, 1.0);
							double num14 = Math.Min(24.0, current9.LearnExposure);
							double num15 = 0.12 + 0.18 * Math.Min(1.0, num14 / 24.0);
							coachingMemoryEntry2.ErrorEwma = ((coachingMemoryEntry2.Songs == 0) ? num13 : (coachingMemoryEntry2.ErrorEwma * (1.0 - num15) + num13 * num15));
							coachingMemoryEntry2.Support = Math.Min(240.0, coachingMemoryEntry2.Support * 0.98 + num14);
							coachingMemoryEntry2.Songs = Math.Min(9999, coachingMemoryEntry2.Songs + 1);
							coachingMemoryEntry2.Occurrences = Math.Min(99999, coachingMemoryEntry2.Occurrences + current9.LearnOccurrences);
							coachingMemoryEntry2.LastSong = Limit(run.SongKey, 160);
							coachingMemoryEntry2.LastStart = Math.Max(0.0, current9.LearnWorst.Start - 0.6);
							coachingMemoryEntry2.LastEnd = Math.Min(Math.Max(run.Duration, current9.LearnWorst.End), current9.LearnWorst.End + 0.6);
							coachingMemoryEntry2.UpdatedUtcTicks = System.DateTime.UtcNow.Ticks;
							num11++;
						}
					}
				}
				finally
				{
					((System.IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose();
				}
				memory.Patterns = new List<CoachingMemoryEntry>((System.Collections.Generic.IEnumerable<CoachingMemoryEntry>)val5.Values);
				memory.Patterns.Sort((Comparison<CoachingMemoryEntry>)((CoachingMemoryEntry a, CoachingMemoryEntry b) => b.UpdatedUtcTicks.CompareTo(a.UpdatedUtcTicks)));
				if (memory.Patterns.Count > 256)
				{
					memory.Patterns.RemoveRange(256, memory.Patterns.Count - 256);
				}
				coachingReport.Learned = num11 > 0;
			}
			coachingReport.HistoryPatterns = (memory?.Patterns?.Count).GetValueOrDefault();
			coachingReport.Summary = (flag ? "本局含练习/变速/跳转,仅供片段复盘,不用于延迟结论和正常游玩记忆。" : (run.Completed ? ((coachingReport.Recommendations.Count == 0) ? "本局没有足够的重复失误证据;继续正常游玩积累对照。" : "先练多次复现的配置;孤立失误已降权,后续同类表现会提高或降低优先级。") : (run.CaptureIncomplete ? "部分判定未完整采集:仅供参考,本局不写入长期记忆。" : "未完整结算:仅供参考,本局不写入长期记忆。")));
			if (run.Completed && run.CaptureIncomplete && !flag)
			{
				coachingReport.Summary += " 已排除未完整采集音符,配置记忆仅采用完整段。";
			}
			if (coachingReport.CaptureCoverage < 0.85)
			{
				coachingReport.ExcludedReason = Join(coachingReport.ExcludedReason, $"有效覆盖 {coachingReport.CaptureCoverage:0%},不足 85%,本局不更新配置记忆");
			}
			if (coachingReport.InterruptedEvents > 0)
			{
				coachingReport.ExcludedReason = Join(coachingReport.ExcludedReason, $"已排除中断附近 {coachingReport.InterruptedEvents} 个判定");
			}
			if (!string.IsNullOrWhiteSpace(run.CaptureNote))
			{
				coachingReport.ExcludedReason = Join(coachingReport.ExcludedReason, Limit(run.CaptureNote, 240));
			}
			return coachingReport;
		}

		private static List<Phrase> BuildPhrases(List<PracticeNote> notes, out Phrase[] byNote, out List<int> order)
		{
			int num = Math.Min(30000, notes.Count);
			byNote = new Phrase[num];
			order = new List<int>(num);
			for (int i = 0; i < num; i++)
			{
				if (double.IsFinite(notes[i].Start) && double.IsFinite(notes[i].End) && float.IsFinite(notes[i].Lane))
				{
					order.Add(i);
				}
			}
			order.Sort((Comparison<int>)((int a, int b) => notes[a].Start.CompareTo(notes[b].Start)));
			List<Phrase> val = new List<Phrase>();
			for (int num2 = 0; num2 < order.Count; num2 += 8)
			{
				int num3 = Math.Min(order.Count, num2 + 8);
				int num4 = 0;
				int num5 = 0;
				int num6 = 0;
				int num7 = 0;
				int num8 = 0;
				int num9 = 0;
				int num10 = 0;
				double num11 = 0.0;
				List<double> val2 = new List<double>();
				float num12 = 99f;
				float num13 = -99f;
				double num14 = 0.0;
				for (int num15 = num2; num15 < num3; num15++)
				{
					PracticeNote practiceNote = notes[order[num15]];
					num12 = Math.Min(num12, practiceNote.Lane);
					num13 = Math.Max(num13, practiceNote.Lane);
					num14 = Math.Max(num14, Math.Max(practiceNote.Start, practiceNote.End));
					if (practiceNote.Surface == PracticeNoteSurface.Side)
					{
						num7++;
					}
					if (practiceNote.Kind == PracticeNoteKind.Sky)
					{
						num8++;
					}
					if (practiceNote.Kind == PracticeNoteKind.Flick)
					{
						num9++;
					}
					if (practiceNote.Kind == PracticeNoteKind.Hold)
					{
						num10++;
					}
					if (num15 == num2)
					{
						continue;
					}
					PracticeNote practiceNote2 = notes[order[num15 - 1]];
					double num16 = practiceNote.Start - practiceNote2.Start;
					double num17 = Math.Sign(practiceNote.Lane - practiceNote2.Lane);
					if (num16 < 0.025)
					{
						num4++;
					}
					else
					{
						val2.Add(num16);
					}
					if (num17 == 0.0)
					{
						num6++;
						continue;
					}
					if (num11 != 0.0 && num17 != num11)
					{
						num5++;
					}
					num11 = num17;
				}
				double num18 = ((val2.Count == 0) ? 0.5 : Median(val2));
				int num19 = ((num18 < 0.12) ? 4 : ((num18 < 0.2) ? 3 : ((num18 < 0.35) ? 2 : ((num18 < 0.6) ? 1 : 0))));
				string text = ((num19 >= 3) ? "高密度" : ((num19 == 2) ? "中密度" : "低密度"));
				string text2 = ((num8 >= 2) ? "空轨移动" : ((num9 >= 2) ? "滑键组合" : ((num7 >= 3) ? "侧轨组合" : ((num10 >= 3) ? "长键衔接" : ((num4 >= 3) ? "多押组合" : ((num6 >= 4) ? "同轨连打" : ((num5 >= 3) ? "交互连打" : ((num13 - num12 >= 3f) ? "跨轨移动" : "顺序短键"))))))));
				string key = $"v1/{text2}/{num19}/{Math.Min(2, num4)}/{num13 - num12 >= 3f}/{Math.Min(2, num10)}/{Math.Min(2, num8)}";
				string text3 = ((text2 == "滑键组合") ? "含 Flick 混合段" : text2);
				Phrase phrase = new Phrase
				{
					Key = key,
					Label = text3 + " · " + text,
					Start = notes[order[num2]].Start,
					End = num14,
					Expected = num3 - num2,
					OrderOffset = num2
				};
				val.Add(phrase);
				for (int num20 = num2; num20 < num3; num20++)
				{
					byNote[order[num20]] = phrase;
				}
			}
			return val;
		}

		private static string Counts(CoachingGradeCounts n)
		{
			return $"小 E {n.SmallExact} / Near {n.Near} / Break {n.Break}";
		}

		private static string NonzeroCounts(CoachingGradeCounts n)
		{
			string text = "";
			if (n.SmallExact > 0)
			{
				text = $"小E {n.SmallExact}";
			}
			if (n.Near > 0)
			{
				text = text + ((text.Length > 0) ? "、" : "") + $"Near {n.Near}";
			}
			if (n.Break > 0)
			{
				text = text + ((text.Length > 0) ? "、" : "") + $"Break {n.Break}";
			}
			return text;
		}

		public static string RecommendationText(CoachingRecommendation item, bool compact = false)
		{
			string text = NonzeroCounts(new CoachingGradeCounts
			{
				SmallExact = item.SmallExact,
				Near = item.Near,
				Break = item.Break
			});
			string types = "";
			Add("Flick", item.NoteOutcomes.Flick);
			Add("Tap", item.NoteOutcomes.Tap);
			Add("Hold", item.NoteOutcomes.Hold);
			Add("Field", item.NoteOutcomes.Field);
			string text2 = ((item.Configuration != null) ? ("练法:" + (compact ? item.Configuration.ShortDrill : item.Advice)) : (string.IsNullOrEmpty(item.Advice) ? PracticeAdvice(item.NoteOutcomes, item.Near, item.Break) : item.Advice));
			return string.Concat(new string[6]
			{
				"本段判定:",
				(text.Length > 0) ? text : "无明显失误",
				"。",
				compact ? "" : "\n",
				(types.Length > 0) ? ("其中," + types + "。\n") : "",
				text2
			});
			[CompilerGenerated]
			void Add(string name, CoachingGradeCounts grades)
			{
				string text3 = NonzeroCounts(grades);
				if (text3.Length > 0)
				{
					types = string.Concat(new string[5]
					{
						types,
						(types.Length > 0) ? ";" : "",
						name,
						":",
						text3
					});
				}
			}
		}

		private static string PracticeAdvice(CoachingTypeCounts types, int near, int breaks)
		{
			if (types.Flick.Break > 0 || types.Flick.Near > 0)
			{
				return "建议:放慢确认滑动方向,再接回地键。";
			}
			if (types.Hold.Break > 0 || types.Hold.Near > 0)
			{
				return "建议:分开练按下和松开,再接回整段。";
			}
			if (types.Field.Break > 0 || types.Field.Near > 0)
			{
				return "建议:跟准天键移动,再加入地键。";
			}
			if (breaks > 0)
			{
				return "建议:以 0.75× 练准落点,稳定后再恢复原速。";
			}
			if (near > 0)
			{
				return "建议:放慢拆清节奏,稳定后逐步提速。";
			}
			return "建议:原速找准节奏,反复出错再慢练。";
		}

		private static string TypeEvidence(CoachingTypeCounts n)
		{
			return $"本段分类型:Flick {Counts(n.Flick)};Tap {Counts(n.Tap)};Hold {Counts(n.Hold)};Field {Counts(n.Field)}。";
		}

		private static void DetectTransient(List<Sample> samples, CoachingReport report)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, HashSet<Phrase>> val = new Dictionary<string, HashSet<Phrase>>();
			Dictionary<string, HashSet<Phrase>> val2 = new Dictionary<string, HashSet<Phrase>>();
			Enumerator<Sample> enumerator = samples.GetEnumerator();
			try
			{
				HashSet<Phrase> val3 = default(HashSet<Phrase>);
				HashSet<Phrase> val4 = default(HashSet<Phrase>);
				while (enumerator.MoveNext())
				{
					Sample current = enumerator.Current;
					if (!val.TryGetValue(current.Phrase.Key, ref val3))
					{
						val3 = new HashSet<Phrase>();
						val[current.Phrase.Key] = val3;
					}
					val3.Add(current.Phrase);
					if (current.Weight >= 1.0)
					{
						if (!val2.TryGetValue(current.Phrase.Key, ref val4))
						{
							val4 = new HashSet<Phrase>();
							val2[current.Phrase.Key] = val4;
						}
						val4.Add(current.Phrase);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			HashSet<Phrase> val5 = default(HashSet<Phrase>);
			for (int i = 0; i < samples.Count; i++)
			{
				if (samples[i].Weight < 1.0)
				{
					continue;
				}
				int j;
				for (j = i; j + 1 < samples.Count && samples[j + 1].Weight >= 1.0 && samples[j + 1].Event.ChartTime - samples[j].Event.ChartTime <= 1.2; j++)
				{
				}
				int num = j - i + 1;
				double num2 = samples[j].Event.ChartTime - samples[i].Event.ChartTime;
				bool flag = i >= 3 && j + 3 < samples.Count;
				for (int k = 1; k <= 3 && flag; k++)
				{
					flag = samples[i - k].Weight < 1.0 && samples[j + k].Weight < 1.0 && samples[i].Event.ChartTime - samples[i - k].Event.ChartTime < 4.0 && samples[j + k].Event.ChartTime - samples[j].Event.ChartTime < 4.0;
				}
				if (flag && num <= 6 && num2 <= 2.0)
				{
					for (int l = i; l <= j; l++)
					{
						Sample sample = samples[l];
						string key = sample.Phrase.Key;
						if (!val2.TryGetValue(key, ref val5) || val5.Count < 2 || !((double)val5.Count / (double)val[key].Count >= 0.35))
						{
							sample.Factor = ((num <= 2) ? 0.35 : 0.6);
							report.TransientEvents++;
						}
					}
				}
				i = j;
			}
			report.SuspectedTransientRate = ((samples.Count == 0) ? 0.0 : ((double)report.TransientEvents / (double)samples.Count));
			report.TransientEvidence = $"短暂异常候选 {report.TransientEvents}/{samples.Count}({report.SuspectedTransientRate:0.0%});前后恢复的孤立/短串失误按 0.35/0.60 降权。可能与操作、识谱、遮挡、疲劳或注意力波动有关,不能从判定证明走神。";
		}

		private static void AnalyzeTiming(CoachingRun run, List<Sample> samples, bool contaminated, CoachingReport report)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_031c: Unknown result type (might be due to invalid IL or missing references)
			if (contaminated || !run.Completed)
			{
				report.TimingTitle = "延迟:本局不作估计";
				report.ExcludedReason = (contaminated ? "包含练习、变速、循环或跳转" : (run.CaptureIncomplete ? "判定采集不完整" : "尚未完整结算"));
				report.TimingEvidence = report.ExcludedReason;
				return;
			}
			List<Sample> val = new List<Sample>();
			List<double> val2 = new List<double>();
			Enumerator<Sample> enumerator = samples.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					Sample current = enumerator.Current;
					CoachingEvent coachingEvent = current.Event;
					if (coachingEvent.TimingValid && double.IsFinite(coachingEvent.OffsetMs) && !(Math.Abs(coachingEvent.OffsetMs) > 250.0) && (int)coachingEvent.Grade < 2 && !(current.Factor < 1.0) && current.Phrase.Observed >= 4 && !(current.Phrase.RawWeight / (double)current.Phrase.Observed > 0.4))
					{
						val.Add(current);
						val2.Add(coachingEvent.OffsetMs);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			if (val2.Count < 24)
			{
				AnalyzeDirection(run, samples, report, val2.Count);
				return;
			}
			double num = Median(val2);
			List<double> val3 = new List<double>(val2.Count);
			Enumerator<double> enumerator2 = val2.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					double current2 = enumerator2.Current;
					val3.Add(Math.Abs(current2 - num));
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
			double num2 = Median(val3);
			double num3 = Math.Max(12.0, 4.4478 * num2);
			List<double> val4 = new List<double>();
			List<double>[] array = new List<double>[4]
			{
				new List<double>(),
				new List<double>(),
				new List<double>(),
				new List<double>()
			};
			HashSet<string> val5 = new HashSet<string>();
			for (int i = 0; i < val.Count; i++)
			{
				Sample sample = val[i];
				if (!(Math.Abs(sample.Event.OffsetMs - num) > num3))
				{
					val4.Add(sample.Event.OffsetMs);
					int num4 = (int)Math.Clamp(sample.Event.ChartTime / Math.Max(1.0, run.Duration) * 4.0, 0.0, 3.0);
					array[num4].Add(sample.Event.OffsetMs);
					val5.Add(sample.Phrase.Key);
				}
			}
			report.TimingSamples = val4.Count;
			if (val4.Count < 24)
			{
				report.TimingTitle = "延迟:稳定样本不足";
				report.TimingEvidence = $"去除异常后仅 {val4.Count} 个有效判定。";
				return;
			}
			num = Median(val4);
			val3.Clear();
			enumerator2 = val4.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					double current3 = enumerator2.Current;
					val3.Add(Math.Abs(current3 - num));
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
			num2 = Median(val3);
			int num5 = 0;
			double num6 = 0.0;
			double num7 = 0.0;
			double num8 = 1.0 / 0.0;
			double num9 = -1.0 / 0.0;
			for (int j = 0; j < 4; j++)
			{
				if (array[j].Count >= 5)
				{
					double num10 = Median(array[j]);
					if (num5 == 0)
					{
						num6 = num10;
					}
					num7 = num10;
					num5++;
					num8 = Math.Min(num8, num10);
					num9 = Math.Max(num9, num10);
				}
			}
			report.DriftMs = num7 - num6;
			report.OffsetMs = num;
			report.SpreadMs = 1.4826 * num2;
			bool flag = num5 >= 3 && num9 - num8 > Math.Max(18.0, report.SpreadMs * 1.5);
			report.HasOffsetEstimate = num5 >= 3;
			report.TimingConfidence = Math.Clamp(Math.Min(1.0, (double)val4.Count / 100.0) * ((double)num5 / 4.0) * ((val5.Count > 1) ? 1.0 : 0.7) * (1.0 - Math.Min(0.7, report.SpreadMs / 100.0)) * (flag ? 0.35 : 1.0), 0.0, 1.0);
			report.SystematicBiasCandidate = report.HasOffsetEstimate && !flag && val5.Count >= 2 && Math.Abs(num) > Math.Max(6.0, report.SpreadMs * 0.35) && report.TimingConfidence >= 0.35;
			string text = ((!run.SignKnown) ? "有符号偏移" : ((num > 0.0) ? "偏晚" : "偏早"));
			report.TimingTitle = ((!report.HasOffsetEstimate) ? "延迟:覆盖不足" : (flag ? "延迟:随进度变化" : ((Math.Abs(num) <= Math.Max(6.0, report.SpreadMs * 0.35)) ? "延迟:未见稳定偏移" : ("延迟:稳定" + text))));
			if (!run.SignKnown && !flag && report.HasOffsetEstimate && Math.Abs(num) > Math.Max(6.0, report.SpreadMs * 0.35))
			{
				report.TimingTitle = "延迟:方向待确认";
			}
			report.TimingEvidence = $"中位数 {num:+0.0;-0.0;0.0} ms,离散 {report.SpreadMs:0.0} ms;{val4.Count} 次,覆盖 {num5}/4 时段、{val5.Count} 类配置。" + (flag ? $"前后变化 {report.DriftMs:+0.0;-0.0;0.0} ms,可能是熟练度/疲劳/音画漂移,单局无法区分。" : "稳定偏移也可能来自个人按键习惯,不能据此断定设备延迟。") + " 不自动修改设置。";
		}

		private static void AnalyzeDirection(CoachingRun run, List<Sample> samples, CoachingReport report, int exactCount)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			int[] array = new int[4];
			int[] array2 = new int[4];
			HashSet<string> val = new HashSet<string>();
			Enumerator<Sample> enumerator = samples.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					Sample current = enumerator.Current;
					CoachingEvent coachingEvent = current.Event;
					if (!(current.Factor < 1.0) && current.Phrase.Observed >= 4 && !(current.Phrase.RawWeight / (double)current.Phrase.Observed > 0.4) && coachingEvent.Grade != CoachingGrade.Break && coachingEvent.Grade != CoachingGrade.Unknown && Math.Abs(coachingEvent.TimingDirection) == 1)
					{
						int num = (int)Math.Clamp(coachingEvent.ChartTime / Math.Max(1.0, run.Duration) * 4.0, 0.0, 3.0);
						if (coachingEvent.TimingDirection < 0)
						{
							array[num]++;
							report.EarlyCount++;
						}
						else
						{
							array2[num]++;
							report.LateCount++;
						}
						val.Add(current.Phrase.Key);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			int num2 = report.EarlyCount + report.LateCount;
			int num3 = 0;
			int num4 = 0;
			bool flag = report.LateCount > report.EarlyCount;
			for (int i = 0; i < 4; i++)
			{
				if (array[i] + array2[i] >= 5)
				{
					num3++;
					if ((double)(flag ? array2[i] : array[i]) / (double)(array[i] + array2[i]) >= 0.65)
					{
						num4++;
					}
				}
			}
			double num5 = ((num2 == 0) ? 0.0 : ((double)Math.Max(report.EarlyCount, report.LateCount) / (double)num2));
			report.TimingSamples = num2;
			report.HasDirectionEstimate = num2 >= 24 && num3 >= 3;
			report.TimingConfidence = Math.Clamp(Math.Min(1.0, (double)num2 / 80.0) * ((double)num3 / 4.0) * ((val.Count > 1) ? 1.0 : 0.7) * Math.Max(0.0, (num5 - 0.5) * 2.0), 0.0, 1.0);
			bool flag2 = report.HasDirectionEstimate && num5 >= 0.72 && num4 == num3;
			report.SystematicBiasCandidate = flag2 && val.Count >= 2 && num5 >= 0.8;
			string text = (flag ? "晚" : "早");
			bool flag3 = num2 >= 24 && num5 >= 0.65;
			report.TimingTitle = ((num2 < 24) ? "早晚倾向:样本不足" : (flag2 ? ("早晚倾向:持续偏" + text) : (flag3 ? ("早晚倾向:" + ((num3 >= 3) ? "本局" : "局部") + "偏" + text) : ((num5 >= 0.55) ? ("早晚倾向:略偏" + text) : "早晚倾向:大致均衡"))));
			report.ExcludedReason = $"当前只有 {exactCount} 个精确时差值,不能换算毫秒或建议偏移数值。";
			double num6 = ((num2 == 0) ? 0.0 : ((double)report.LateCount / (double)num2));
			report.TimingEvidence = ((num2 < 24) ? $"已记录 {num2} 次早晚判定,样本较少,暂不作整体判断。" : ($"早晚判定中 Late 占 {num6:0%}(Early {report.EarlyCount} / Late {report.LateCount})。" + (flag2 ? $"{num3} 个有效时段都偏{text},倾向比较持续。" : (flag3 ? $"整体偏{text};{num3} 个有效时段中 {num4} 个同向,部分段落的倾向不同。" : "整体较为接近,部分段落仍可能有不同的早晚倾向。")) + ((num3 < 3) ? "样本集中在少数段落,还不能代表整首。" : "")));
			report.TimingEvidence = report.TimingEvidence + ((flag3 || flag2) ? "建议:再对照几首熟悉的曲子;若持续同向,再检查偏移设置。" : "建议:先练推荐片段,节奏稳定后再看早晚倾向。") + "早晚等级不能换算毫秒,也不能单凭一局确定设备延迟。";
		}

		private static double Weight(CoachingGrade grade)
		{
			return grade switch
			{
				CoachingGrade.Break => 2.2, 
				CoachingGrade.Near => 1.0, 
				CoachingGrade.SmallExact => 0.35, 
				_ => 0.0, 
			};
		}

		private static double Median(List<double> values)
		{
			values.Sort();
			int count = values.Count;
			if (count != 0)
			{
				if (count % 2 != 0)
				{
					return values[count / 2];
				}
				return (values[count / 2 - 1] + values[count / 2]) * 0.5;
			}
			return 0.0;
		}

		private static Dictionary<string, CoachingMemoryEntry> MemoryMap(CoachingMemory memory)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, CoachingMemoryEntry> val = new Dictionary<string, CoachingMemoryEntry>((IEqualityComparer<string>)(object)StringComparer.Ordinal);
			if (memory?.Patterns == null)
			{
				return val;
			}
			Enumerator<CoachingMemoryEntry> enumerator = memory.Patterns.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					CoachingMemoryEntry current = enumerator.Current;
					if (current != null && !string.IsNullOrEmpty(current.Key) && current.Key.Length <= 160 && double.IsFinite(current.ErrorEwma) && current.ErrorEwma >= 0.0 && current.ErrorEwma <= 1.0 && double.IsFinite(current.Support) && current.Support >= 0.0 && current.Support <= 240.0 && current.Songs >= 0 && current.Songs <= 9999)
					{
						val[current.Key] = current;
					}
				}
				return val;
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static string Limit(string text, int length)
		{
			if (!string.IsNullOrEmpty(text))
			{
				if (text.Length <= length)
				{
					return text;
				}
				return text.Substring(0, length);
			}
			return "";
		}

		private static string Join(string first, string next)
		{
			if (!string.IsNullOrEmpty(first))
			{
				return first + ";" + next;
			}
			return next;
		}
	}
	public static class CoachingMemoryStore
	{
		private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
		{
			IncludeFields = true
		};

		public static CoachingMemory Load(string path)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			try
			{
				FileInfo val = new FileInfo(path);
				if (!((FileSystemInfo)val).Exists || val.Length > 524288)
				{
					return new CoachingMemory();
				}
				CoachingMemory coachingMemory = JsonSerializer.Deserialize<CoachingMemory>(File.ReadAllText(path), Options);
				if (coachingMemory == null || coachingMemory.Version != 1 || coachingMemory.Patterns == null)
				{
					return new CoachingMemory();
				}
				coachingMemory.Patterns.RemoveAll((Predicate<CoachingMemoryEntry>)delegate(CoachingMemoryEntry e)
				{
					if (e != null && !string.IsNullOrEmpty(e.Key) && e.Key.Length <= 160)
					{
						string label = e.Label;
						if (label == null || label.Length <= 160)
						{
							string lastSong = e.LastSong;
							if ((lastSong == null || lastSong.Length <= 160) && double.IsFinite(e.ErrorEwma) && !(e.ErrorEwma < 0.0) && !(e.ErrorEwma > 1.0) && double.IsFinite(e.Support) && !(e.Support < 0.0) && !(e.Support > 240.0) && e.Songs >= 0 && e.Songs <= 9999 && e.Occurrences >= 0)
							{
								return e.Occurrences > 99999;
							}
						}
					}
					return true;
				});
				coachingMemory.Patterns.Sort((Comparison<CoachingMemoryEntry>)((CoachingMemoryEntry a, CoachingMemoryEntry b) => b.UpdatedUtcTicks.CompareTo(a.UpdatedUtcTicks)));
				if (coachingMemory.Patterns.Count > 256)
				{
					coachingMemory.Patterns.RemoveRange(256, coachingMemory.Patterns.Count - 256);
				}
				return coachingMemory;
			}
			catch (System.Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException || ex is ArgumentException || ex is NotSupportedException)
			{
				return new CoachingMemory();
			}
		}

		public static bool Save(string path, CoachingMemory memory)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			string text = null;
			try
			{
				if (memory == null || memory.Version != 1 || memory.Patterns == null)
				{
					return false;
				}
				if (memory.Patterns.Count > 256)
				{
					return false;
				}
				string fullPath = Path.GetFullPath(path);
				Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
				Guid val = Guid.NewGuid();
				text = fullPath + "." + ((Guid)(ref val)).ToString("N") + ".tmp";
				string text2 = JsonSerializer.Serialize<CoachingMemory>(memory, Options);
				if (text2.Length > 262144)
				{
					return false;
				}
				File.WriteAllText(text, text2);
				File.Move(text, fullPath, true);
				text = null;
				return true;
			}
			catch (System.Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException || ex is ArgumentException || ex is NotSupportedException)
			{
				return false;
			}
			finally
			{
				if (text != null)
				{
					try
					{
						File.Delete(text);
					}
					catch
					{
					}
				}
			}
		}
	}
	public sealed class CoachingCapture
	{
		private readonly struct NoteKey : IEquatable<NoteKey>
		{
			private readonly long _start;

			private readonly long _end;

			private readonly int _lane;

			private readonly int _endLane;

			private readonly int _width;

			private readonly int _endWidth;

			private readonly int _kind;

			private readonly int _surface;

			private readonly int _right;

			public NoteKey(PracticeNote n)
			{
				_start = (long)Math.Round(n.Start * 1000.0);
				_end = (long)Math.Round(n.End * 1000.0);
				_lane = (int)Math.Round((double)(n.Lane * 4096f));
				_endLane = (int)Math.Round((double)(n.EndLane * 4096f));
				_width = (int)Math.Round((double)(n.Width * 4096f));
				_endWidth = (int)Math.Round((double)(n.EndWidth * 4096f));
				_kind = (int)n.Kind;
				_surface = (int)n.Surface;
				_right = (n.RightFlick ? 1 : 0);
			}

			public bool Equals(NoteKey n)
			{
				if (_start == n._start && _end == n._end && _lane == n._lane && _endLane == n._endLane && _width == n._width && _endWidth == n._endWidth && _kind == n._kind && _surface == n._surface)
				{
					return _right == n._right;
				}
				return false;
			}

			public override bool Equals(object value)
			{
				if (value is NoteKey n)
				{
					return Equals(n);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return HashCode.Combine<long, long, int, int, int, int, int, int>(_start, _end, _lane, _endLane, _width, _endWidth, _kind, HashCode.Combine<int, int>(_surface, _right));
			}
		}

		private const int MaximumRecords = 120000;

		private Il2CppStructArray<_cH> _array;

		private List<_fA> _source;

		private Dictionary<long, Memory<_cH>> _slices;

		private Dictionary<NoteKey, int> _available;

		private System.Collections.Generic.IReadOnlyList<PracticeNote> _notes;

		private int[] _byRecord;

		private int[] _order;

		private int[] _nextNote;

		private double[] _times;

		private uint[] _seen;

		private byte[] _grades;

		private sbyte[] _directions;

		private readonly List<int> _pending = new List<int>(256);

		private System.IntPtr _playerPointer;

		private int _base;

		private int _count;

		private int _sourceIndex;

		private int _metadataIndex;

		private int _nextDue;

		private int _pendingCursor;

		private int _mappedRecordCount;

		private bool _bound;

		private bool _ready;

		private bool _failed;

		private long _nextBind;

		[field: CompilerGenerated]
		public string LastError
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		} = "";

		[field: CompilerGenerated]
		public int CapturedRecords
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		public void Reset()
		{
			ReleaseNative();
			_available = null;
			_notes = null;
			_byRecord = (_order = (_nextNote = null));
			_times = null;
			_seen = null;
			_grades = null;
			_directions = null;
			_pending.Clear();
			if (_pending.Capacity > 4096)
			{
				_pending.Capacity = 256;
			}
			int mappedRecordCount = (CapturedRecords = 0);
			_base = (_count = (_sourceIndex = (_metadataIndex = (_nextDue = (_pendingCursor = (_mappedRecordCount = mappedRecordCount))))));
			_bound = (_ready = (_failed = false));
			LastError = "";
			_nextBind = 0L;
		}

		private void ReleaseNative()
		{
			_array = null;
			_source = null;
			_slices = null;
			_playerPointer = System.IntPtr.Zero;
		}

		public void Invalidate(string reason)
		{
			_failed = true;
			LastError = reason ?? "本局采集被中断";
			ReleaseNative();
		}

		public void Observe(LogicalNotePlayer player, System.Collections.Generic.IReadOnlyList<PracticeNote> notes, double time, int state)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			if ((state != 2 && state != 3) || _failed || (Object)(object)player == (Object)null || notes == null || ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count == 0)
			{
				return;
			}
			try
			{
				if (!_bound)
				{
					if (Stopwatch.GetTimestamp() < _nextBind)
					{
						return;
					}
					Bind(player, notes);
					LastError = "";
				}
				if (((Il2CppObjectBase)player).Pointer != _playerPointer)
				{
					throw new InvalidOperationException("谱面已替换,停止读取旧局判定");
				}
				long deadline = Stopwatch.GetTimestamp() + (long)((double)Stopwatch.Frequency * 0.00065);
				if (!_ready)
				{
					Prepare(deadline);
				}
				else if (state == 3 && double.IsFinite(time))
				{
					Poll(time, deadline);
				}
			}
			catch (System.Exception ex)
			{
				LastError = ex.Message;
				if (_bound)
				{
					_failed = true;
					ReleaseNative();
				}
				else
				{
					ReleaseNative();
					_nextBind = Stopwatch.GetTimestamp() + Stopwatch.Frequency / 4;
				}
			}
		}

		private void Bind(LogicalNotePlayer player, System.Collections.Generic.IReadOnlyList<PracticeNote> notes)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			if (((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count > 30000)
			{
				throw new InvalidOperationException("谱面数量超过分析上限");
			}
			System.Collections.Generic.IReadOnlyList<PracticeNote> notes2;
			if (!(notes is NoteBuffer))
			{
				System.Collections.Generic.IReadOnlyList<PracticeNote> readOnlyList = (System.Collections.Generic.IReadOnlyList<PracticeNote>)CopyNotes(notes);
				notes2 = readOnlyList;
			}
			else
			{
				notes2 = notes;
			}
			_notes = notes2;
			_T ve = player._ve;
			Memory<_cH> val = ((ve != null) ? ve._cd : null);
			Object val2 = val?._object;
			if (val2 == null)
			{
				throw new InvalidOperationException("判定数组尚未就绪");
			}
			NativeJudgementArray.Validate(((Il2CppObjectBase)val2).Pointer);
			_array = new Il2CppStructArray<_cH>(((Il2CppObjectBase)val2).Pointer);
			_base = val._index;
			_count = ve._Dd;
			int num = val._length & 0x7FFFFFFF;
			if (_base < 0 || _count <= 0 || _count > 120000 || _count > num || _base > ((Il2CppArrayBase<_cH>)(object)_array).Length - _count)
			{
				throw new InvalidOperationException("判定数组范围不正确");
			}
			_source = player._Gb()?.Item2;
			_slices = ve._XC;
			if (_source == null || _slices == null || _source.Count > 120000)
			{
				throw new InvalidOperationException("谱面关联数据尚未就绪");
			}
			_grades = new byte[((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count];
			_directions = new sbyte[((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count];
			_byRecord = new int[_count];
			System.Array.Fill<int>(_byRecord, -1);
			_order = new int[_count];
			_times = new double[_count];
			_seen = new uint[(_count + 31) / 32];
			_available = new Dictionary<NoteKey, int>(((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count);
			_nextNote = new int[((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count];
			int num3 = default(int);
			for (int num2 = ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count - 1; num2 >= 0; num2--)
			{
				NoteKey noteKey = new NoteKey(notes[num2]);
				_nextNote[num2] = (_available.TryGetValue(noteKey, ref num3) ? num3 : (-1));
				_available[noteKey] = num2;
			}
			_playerPointer = ((Il2CppObjectBase)player).Pointer;
			_bound = true;
		}

		private unsafe void Prepare(long deadline)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected I4, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected I4, but got Unknown
			//IL_0305: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Expected I4, but got Unknown
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			int num4 = default(int);
			while (_source != null && _sourceIndex < _source.Count && num < 64)
			{
				_fA val = _source[_sourceIndex++];
				int num2 = (int)val._Ae;
				int num3 = (int)val._be;
				if (num2 >= 1 && num2 <= 4 && (num3 == 1 || num3 == 2 || num3 == 4 || num3 == 5))
				{
					float x = ((num2 == 1) ? ((float)val._Ce._KD) : Rational(val._Ce));
					float endX = ((num2 == 1) ? ((float)val._de._KD) : Rational(val._de));
					float width = ((num2 == 1) ? ((float)val._De._KD) : Rational(val._De));
					float endWidth = ((num2 == 1) ? ((float)val._ee._KD) : Rational(val._ee));
					PracticeNote n = ChartProjection.Map(num2, num3, (double)val._Be * 0.001, (double)Math.Max(val._Be, val._ce) * 0.001, x, endX, width, endWidth, (int)val._he);
					NoteKey noteKey = new NoteKey(n);
					if (_available.TryGetValue(noteKey, ref num4) && num4 >= 0)
					{
						_available[noteKey] = _nextNote[num4];
						if (_slices.ContainsKey(val._ZD))
						{
							Memory<_cH> obj = _slices[val._ZD];
							Object val2 = obj?._object;
							int num5 = (obj?._index ?? 0) - _base;
							int num6 = (obj?._length ?? 0) & 0x7FFFFFFF;
							System.IntPtr? intPtr = ((val2 != null) ? new System.IntPtr?(((Il2CppObjectBase)val2).Pointer) : ((System.IntPtr?)null));
							System.IntPtr pointer = ((Il2CppObjectBase)_array).Pointer;
							if (!intPtr.HasValue || (intPtr.HasValue && intPtr.GetValueOrDefault() != pointer) || num5 < 0 || num6 < 0 || num5 > _count - num6)
							{
								throw new InvalidOperationException("音符判定切片与本局数组不匹配");
							}
							for (int i = num5; i < num5 + num6; i++)
							{
								if (_byRecord[i] < 0)
								{
									_byRecord[i] = num4;
								}
							}
						}
					}
					if ((num & 7) == 7 && Stopwatch.GetTimestamp() >= deadline)
					{
						return;
					}
				}
				num++;
			}
			if (_sourceIndex < _source.Count)
			{
				return;
			}
			Il2CppStructArray<_cH> array = _array;
			try
			{
				System.ReadOnlySpan<JudgementRecord> readOnlySpan = NativeJudgementArray.ReadOnly(array);
				int num7 = 0;
				while (_metadataIndex < _count && num7 < 512)
				{
					int num8 = _metadataIndex++;
					JudgementRecord judgementRecord = *(JudgementRecord*)readOnlySpan[_base + num8];
					if (judgementRecord.Id != num8 || !double.IsFinite(judgementRecord.AbsoluteMs))
					{
						throw new InvalidOperationException("判定编号或时间与本局数组不一致");
					}
					_order[num8] = num8;
					_times[num8] = judgementRecord.AbsoluteMs * 0.001;
					if ((num7 & 0x1F) == 31 && Stopwatch.GetTimestamp() >= deadline)
					{
						return;
					}
					num7++;
				}
			}
			finally
			{
				GC.KeepAlive((object)array);
			}
			if (_metadataIndex < _count)
			{
				return;
			}
			System.Array.Sort<int>(_order, (Comparison<int>)([CompilerGenerated] (int a, int b) => _times[a].CompareTo(_times[b])));
			_mappedRecordCount = 0;
			for (int num9 = 0; num9 < _count; num9++)
			{
				if (_byRecord[num9] >= 0)
				{
					_mappedRecordCount++;
				}
			}
			if (_mappedRecordCount == 0)
			{
				throw new InvalidOperationException("判定数组已读取,但没有匹配到谱面音符");
			}
			_available = null;
			_nextNote = null;
			_source = null;
			_slices = null;
			_ready = true;
		}

		private unsafe void Poll(double time, long deadline)
		{
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			Il2CppStructArray<_cH> array = _array;
			try
			{
				System.ReadOnlySpan<JudgementRecord> readOnlySpan = NativeJudgementArray.ReadOnly(array);
				int num = 0;
				while (_nextDue < _count && _times[_order[_nextDue]] <= time + 1.0 && num < 512)
				{
					int num2 = _order[_nextDue++];
					if (_byRecord[num2] >= 0)
					{
						_pending.Add(num2);
					}
					num++;
				}
				int num3 = Math.Min(512, _pending.Count);
				for (int i = 0; i < num3; i++)
				{
					if (_pending.Count <= 0)
					{
						break;
					}
					if (_pendingCursor >= _pending.Count)
					{
						_pendingCursor = 0;
					}
					int num4 = _pending[_pendingCursor];
					JudgementRecord judgementRecord = *(JudgementRecord*)readOnlySpan[_base + num4];
					if (judgementRecord.Id != num4)
					{
						throw new InvalidOperationException("判定数组已被另一局替换");
					}
					byte result = judgementRecord.Result;
					if (judgementRecord.Finalized != 0 && result >= 1 && result <= 6)
					{
						uint num5 = (uint)(1 << num4);
						int num6 = num4 >> 5;
						if ((_seen[num6] & num5) == 0)
						{
							_seen[num6] |= num5;
							CapturedRecords++;
							int num7 = _byRecord[num4];
							int num8 = Severity(result);
							int num9 = Severity(_grades[num7]);
							sbyte b = (sbyte)Direction(result);
							if (num8 > num9)
							{
								_grades[num7] = result;
								_directions[num7] = b;
							}
							else if (num8 == num9 && _directions[num7] != b)
							{
								_directions[num7] = 0;
							}
						}
						List<int> pending = _pending;
						int pendingCursor = _pendingCursor;
						List<int> pending2 = _pending;
						pending[pendingCursor] = pending2[pending2.Count - 1];
						_pending.RemoveAt(_pending.Count - 1);
					}
					else
					{
						_pendingCursor++;
					}
					if ((i & 0x1F) == 31 && Stopwatch.GetTimestamp() >= deadline)
					{
						break;
					}
				}
			}
			finally
			{
				GC.KeepAlive((object)array);
			}
		}

		public CoachingRun Finish(string key, string title, double duration, bool practice, bool completed)
		{
			ReleaseNative();
			bool flag = _ready && CapturedRecords == _mappedRecordCount;
			CoachingRun coachingRun = new CoachingRun
			{
				SongKey = (key ?? ""),
				SongTitle = (title ?? ""),
				Duration = duration,
				Completed = (completed && _ready && !_failed),
				CaptureIncomplete = (completed && (!flag || _failed)),
				PracticeUsed = practice,
				SignKnown = true
			};
			coachingRun.Notes = (_notes as List<PracticeNote>) ?? ((_notes != null) ? CopyNotes(_notes) : new List<PracticeNote>());
			coachingRun.CaptureNote = LastError;
			if (_ready && !flag)
			{
				for (int i = 0; i < _count; i++)
				{
					if (_byRecord[i] >= 0 && (_seen[i >> 5] & (uint)(1 << i)) == 0)
					{
						int num = _byRecord[i];
						if ((_grades[num] & 0x80) == 0)
						{
							_grades[num] |= 128;
							coachingRun.ExcludedCaptureNotes++;
						}
					}
				}
				coachingRun.MissingJudgementRecords = _mappedRecordCount - CapturedRecords;
				coachingRun.CaptureNote = coachingRun.CaptureNote + ((coachingRun.CaptureNote.Length > 0) ? ";" : "") + $"已排除 {coachingRun.ExcludedCaptureNotes} 个未完整采集音符(缺 {coachingRun.MissingJudgementRecords} 条判定记录);配置记忆只采用完整采集段";
			}
			if (_grades != null)
			{
				for (int j = 0; j < _grades.Length; j++)
				{
					if (_grades[j] != 0 && (_grades[j] & 0x80) == 0)
					{
						PracticeNote practiceNote = _notes[j];
						coachingRun.Events.Add(new CoachingEvent
						{
							NoteIndex = j,
							ChartTime = practiceNote.Start,
							Grade = Grade(_grades[j]),
							TimingDirection = ((practiceNote.Kind == PracticeNoteKind.Tap) ? _directions[j] : 0),
							TimingValid = false,
							Rate = 1.0
						});
					}
				}
			}
			if (!_ready && coachingRun.CaptureNote.Length == 0)
			{
				coachingRun.CaptureNote = "谱面未完成载入或提前退出,按已采集判定展示,不学习缺失数据";
			}
			Reset();
			return coachingRun;
		}

		private static List<PracticeNote> CopyNotes(System.Collections.Generic.IReadOnlyList<PracticeNote> notes)
		{
			List<PracticeNote> val = new List<PracticeNote>(((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count);
			for (int i = 0; i < ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count; i++)
			{
				val.Add(notes[i]);
			}
			return val;
		}

		internal static CoachingGrade Grade(byte n)
		{
			switch (n)
			{
			default:
				return CoachingGrade.Unknown;
			case 6:
				return CoachingGrade.Exact;
			case 4:
			case 5:
				return CoachingGrade.SmallExact;
			case 2:
			case 3:
				return CoachingGrade.Near;
			case 1:
				return CoachingGrade.Break;
			}
		}

		internal static int Direction(byte n)
		{
			switch (n)
			{
			default:
				return 0;
			case 3:
			case 5:
				return 1;
			case 2:
			case 4:
				return -1;
			}
		}

		private static int Severity(byte n)
		{
			switch (n)
			{
			default:
				return 0;
			case 4:
			case 5:
				return 1;
			case 2:
			case 3:
				return 2;
			case 1:
				return 3;
			case 0:
				return -1;
			}
		}

		private static float Rational(_BA n)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (n._lD == 0)
			{
				return n._LD;
			}
			return (float)n._KD / (float)n._lD;
		}
	}
	public sealed class CoachingController
	{
		private readonly object _memoryLock = new object();

		private readonly string _directory = Path.Combine(Paths.ConfigPath, "InFalsusPractice", "coaching");

		private CoachingMemory _memory;

		private System.Threading.Tasks.Task<CoachingReport> _pending;

		private CoachingRun _queued;

		private int _epoch;

		private int _pendingEpoch;

		private int _queuedEpoch;

		public void NewRun()
		{
			_epoch++;
			CoachingView.Clear();
		}

		public void CloseView()
		{
			_epoch++;
			CoachingView.Clear();
		}

		public void Submit(CoachingRun run)
		{
			if (run != null)
			{
				if (_pending != null)
				{
					_queued = run;
					_queuedEpoch = _epoch;
				}
				else
				{
					Start(run, _epoch);
				}
			}
		}

		private void Start(CoachingRun run, int epoch)
		{
			_pendingEpoch = epoch;
			_pending = System.Threading.Tasks.Task.Run<CoachingReport>((Func<CoachingReport>)delegate
			{
				//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d1: Expected O, but got Unknown
				lock (_memoryLock)
				{
					if (_memory == null)
					{
						_memory = CoachingMemoryStore.Load(Path.Combine(_directory, "patterns-v1.json"));
					}
					CoachingReport coachingReport = CoachingAnalysis.Analyze(run, _memory);
					try
					{
						Directory.CreateDirectory(_directory);
						if (coachingReport.Learned && !CoachingMemoryStore.Save(Path.Combine(_directory, "patterns-v1.json"), _memory))
						{
							coachingReport.Learned = false;
							coachingReport.ExcludedReason += ";长期记忆写入失败,本次只在内存中更新。";
						}
						JsonSerializerOptions val = new JsonSerializerOptions
						{
							IncludeFields = true,
							WriteIndented = true
						};
						string text = Path.Combine(_directory, "last-report.json");
						string text2 = text + ".tmp";
						File.WriteAllText(text2, JsonSerializer.Serialize<CoachingReport>(coachingReport, val));
						File.Move(text2, text, true);
					}
					catch (System.Exception ex)
					{
						coachingReport.ExcludedReason = coachingReport.ExcludedReason + ((coachingReport.ExcludedReason.Length > 0) ? ";" : "") + "本次记忆保存失败:" + ((MemberInfo)ex.GetType()).Name;
					}
					return coachingReport;
				}
			});
		}

		public void Poll()
		{
			if (_pending != null && ((System.Threading.Tasks.Task)_pending).IsCompleted)
			{
				System.Threading.Tasks.Task<CoachingReport> pending = _pending;
				_pending = null;
				if (((System.Threading.Tasks.Task)pending).IsFaulted)
				{
					Plugin.Logger.LogWarning((object)("结算分析不可用:" + ((System.Exception)(object)((System.Threading.Tasks.Task)pending).Exception)?.GetBaseException().Message));
				}
				else if (!((System.Threading.Tasks.Task)pending).IsCanceled && _pendingEpoch == _epoch)
				{
					CoachingView.SetReport(pending.Result);
				}
				if (_queued != null)
				{
					CoachingRun queued = _queued;
					_queued = null;
					Start(queued, _queuedEpoch);
				}
			}
		}
	}
	public sealed class CoachingInterruptions
	{
		private readonly List<ValueTuple<double, double>> _ranges = new List<ValueTuple<double, double>>(16);

		private double _chart;

		private double _wall;

		private bool _seen;

		private bool _blocked;

		public int Count => _ranges.Count;

		[field: CompilerGenerated]
		public bool Overflowed
		{
			[CompilerGenerated]
			get;
			[CompilerGenerated]
			private set;
		}

		public void Reset()
		{
			_ranges.Clear();
			bool blocked = (Overflowed = false);
			_seen = (_blocked = blocked);
			_chart = (_wall = 0.0);
		}

		public void Observe(double chart, double wall, bool paused, bool focused)
		{
			if (double.IsFinite(chart) && double.IsFinite(wall))
			{
				bool flag = paused || !focused;
				if (flag || _blocked || (_seen && wall - _wall > 0.25))
				{
					Add(_seen ? _chart : chart, chart);
				}
				_seen = true;
				_chart = chart;
				_wall = wall;
				_blocked = flag;
			}
		}

		private void Add(double from, double to)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			double num = Math.Max(0.0, Math.Min(from, to));
			double num2 = Math.Max(0.0, Math.Max(from, to));
			if (_ranges.Count > 0)
			{
				List<ValueTuple<double, double>> ranges = _ranges;
				ValueTuple<double, double> val = ranges[ranges.Count - 1];
				if (num <= val.Item2 + 0.05 && num2 >= val.Item1 - 0.05)
				{
					List<ValueTuple<double, double>> ranges2 = _ranges;
					ranges2[ranges2.Count - 1] = new ValueTuple<double, double>(Math.Min(num, val.Item1), Math.Max(num2, val.Item2));
					return;
				}
			}
			if (_ranges.Count >= 64)
			{
				Overflowed = true;
			}
			else
			{
				_ranges.Add(new ValueTuple<double, double>(num, num2));
			}
		}

		public bool Contains(double time)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			Enumerator<ValueTuple<double, double>> enumerator = _ranges.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					ValueTuple<double, double> current = enumerator.Current;
					if (time >= current.Item1 - 1.0 && time <= current.Item2 + 1.0)
					{
						return true;
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			return false;
		}
	}
	public readonly struct CoachingLayout
	{
		public const float Width = 860f;

		public const float Height = 230f;

		public const float TabWidth = 186f;

		public const float TabHeight = 32f;

		public readonly HudArea Area;

		public readonly float Scale;

		public readonly bool Compact;

		public bool Visible => Area.Valid;

		public CoachingLayout(int width, int height, bool collapsed, HudArea[] protectedAreas, float preferredX = -1f, float preferredY = -1f)
		{
			Area = default(HudArea);
			Scale = 1f;
			Compact = collapsed;
			if (width < 320 || height < 240)
			{
				return;
			}
			HudArea hudArea = Band(width, height);
			float num = Math.Min(Math.Clamp((float)height / 980f, 0.5f, 1.35f), Math.Min((hudArea.Right - hudArea.Left - 1f) / 860f, (hudArea.Bottom - hudArea.Top - 1f) / 230f));
			if (!collapsed)
			{
				for (float num2 = num; num2 >= 0.499f; num2 -= 0.01f)
				{
					if (TryPlace(width, height, 860f * num2, 230f * num2, protectedAreas, preferredX, preferredY, out var result))
					{
						Area = result;
						Scale = num2;
						return;
					}
				}
			}
			Compact = true;
			Scale = num;
			if (TryPlace(width, height, 186f * num, 32f * num, protectedAreas, preferredX, preferredY, out var result2))
			{
				Area = result2;
			}
		}

		public static bool IsFree(HudArea area, int width, int height, HudArea[] protectedAreas)
		{
			if (!area.Valid || area.Left < 8f || area.Top < 8f || area.Right > (float)(width - 8) || area.Bottom > (float)(height - 8))
			{
				return false;
			}
			HudArea hudArea = Band(width, height);
			if (area.Left < hudArea.Left || area.Right > hudArea.Right || area.Top < hudArea.Top || area.Bottom > hudArea.Bottom)
			{
				return false;
			}
			if (protectedAreas != null)
			{
				for (int i = 0; i < protectedAreas.Length; i++)
				{
					HudArea hudArea2 = protectedAreas[i];
					if (hudArea2.Valid && area.Left < hudArea2.Right + 6f && area.Right > hudArea2.Left - 6f && area.Top < hudArea2.Bottom + 6f && area.Bottom > hudArea2.Top - 6f)
					{
						return false;
					}
				}
			}
			return true;
		}

		private static HudArea Band(int width, int height)
		{
			return new HudArea((float)width * 0.301f, (float)height * 0.628f, (float)width * 0.696f, (float)height * 0.8f);
		}

		public static HudArea CharacterProtection(HudArea projected, int width)
		{
			if (!projected.Valid)
			{
				return default(HudArea);
			}
			return new HudArea(projected.Left, projected.Top, Math.Min(projected.Right, (float)width * 0.334f), projected.Bottom);
		}

		private static bool TryPlace(int width, int height, float panelWidth, float panelHeight, HudArea[] protectedAreas, float preferredX, float preferredY, out HudArea result)
		{
			result = default(HudArea);
			int num = ((protectedAreas != null) ? protectedAreas.Length : 0);
			bool flag = panelHeight < (float)height * 0.06f;
			HudArea hudArea = Band(width, height);
			float left = hudArea.Left;
			float top = hudArea.Top;
			float right = hudArea.Right;
			float bottom = hudArea.Bottom;
			if (right - left < panelWidth || bottom - top < panelHeight)
			{
				return false;
			}
			float num2 = ((preferredX >= 0f) ? preferredX : ((left + right - panelWidth) * 0.5f));
			float num3 = ((preferredY >= 0f) ? preferredY : (flag ? (bottom - panelHeight - 4f) : ((top + bottom - panelHeight) * 0.5f)));
			float num4 = 3.4028235E+38f;
			int num5 = num * 2 + 3;
			for (int i = 0; i < num5; i++)
			{
				float num6 = Math.Clamp(i switch
				{
					2 => right - panelWidth, 
					1 => left, 
					0 => num2, 
					_ => ((i - 3) % 2 == 0) ? (protectedAreas[(i - 3) / 2].Left - panelWidth - 7f) : (protectedAreas[(i - 3) / 2].Right + 7f), 
				}, left, Math.Max(left, right - panelWidth));
				for (int j = 0; j < num5; j++)
				{
					float num7 = Math.Clamp(j switch
					{
						2 => bottom - panelHeight, 
						1 => top, 
						0 => num3, 
						_ => ((j - 3) % 2 == 0) ? (protectedAreas[(j - 3) / 2].Top - panelHeight - 7f) : (protectedAreas[(j - 3) / 2].Bottom + 7f), 
					}, top, Math.Max(top, bottom - panelHeight));
					HudArea hudArea2 = new HudArea(num6, num7, num6 + panelWidth, num7 + panelHeight);
					if (IsFree(hudArea2, width, height, protectedAreas))
					{
						float num8 = Math.Abs(num6 - num2) + Math.Abs(num7 - num3) * 1.25f;
						if (!(num8 >= num4))
						{
							num4 = num8;
							result = hudArea2;
						}
					}
				}
			}
			return result.Valid;
		}
	}
	public sealed class CoachingModule
	{
		private readonly CoachingController _controller = new CoachingController();

		internal readonly CoachingResultObserver Results = new CoachingResultObserver();

		internal readonly CoachingCapture Capture = new CoachingCapture();

		internal readonly CoachingInterruptions Interruptions = new CoachingInterruptions();

		internal bool Pending;

		internal bool CaptureStartedLogged;

		internal string CaptureErrorLogged = "";

		internal double LastPlayingTime;

		public void NewRun()
		{
			_controller.NewRun();
			Results.Clear();
			Capture.Reset();
			Interruptions.Reset();
			Pending = (CaptureStartedLogged = false);
			CaptureErrorLogged = "";
			LastPlayingTime = 0.0;
		}

		public void Poll()
		{
			_controller.Poll();
		}

		public void CloseView()
		{
			_controller.CloseView();
		}

		public void ExitResults()
		{
			Results.Close();
			Results.TakeExit();
			CloseView();
		}

		public void Draw(double now, bool hidden, bool enabled)
		{
			bool inResult = enabled && Results.Read(now);
			if (Results.TakeExit())
			{
				CloseView();
			}
			CoachingView.Draw(inResult, hidden, Results.Protected);
		}

		public void Finish(string chartKey, string title, double duration, bool tainted, bool mirrored = false)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			if (!Pending)
			{
				Capture.Reset();
				return;
			}
			Pending = false;
			try
			{
				CoachingRun coachingRun = Capture.Finish(chartKey, title, duration, tainted, LastPlayingTime >= Math.Max(0.0, duration - 2.0));
				coachingRun.Notes = ChartMirror.ForReview(coachingRun.Notes, mirrored);
				Enumerator<CoachingEvent> enumerator = coachingRun.Events.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						CoachingEvent current = enumerator.Current;
						if (Interruptions.Contains(current.ChartTime))
						{
							current.Flags |= CoachingFlags.PausedBoundary;
						}
					}
				}
				finally
				{
					((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
				if (Interruptions.Overflowed)
				{
					coachingRun.Completed = false;
					coachingRun.CaptureNote += ";中断次数超过分析上限,本局不学习。";
				}
				_controller.Submit(coachingRun);
				Plugin.LogInfo(string.Concat(new string[8]
				{