Game/LocalBoard.cs
using System;
using System.Collections.Generic;

/// <summary>
/// 本地持久化排行榜(M5):全服榜的离线兜底。每局结束把结算榜并入
/// FileSystem.Data 的 JSON(同 SteamId 只留最好成绩),读 Top10 展示。
/// 云端(ScoreUploader)拿不到数据时自动用它——未发布包后端无身份是常态(实测)。
/// ⚠️ Entry 必须是"公共属性":Sandbox.Json 序列化不吃公共字段
/// (私有嵌套类 + 字段实测写出全空榜,Top10 全是 0)。
/// </summary>
public static class LocalBoard
{
	public sealed class Entry
	{
		public long SteamId { get; set; }
		public string Name { get; set; }
		public float Mass { get; set; }
	}

	const string SavePath = "cr_local_board.json";

	/// <summary> 把本局结算榜并入本地榜(同 SteamId 保留最好成绩;封顶 50 条) </summary>
	public static void Record( ScoreWire[] rows )
	{
		try
		{
			if ( rows is null || rows.Length == 0 ) return;

			var list = Load();
			foreach ( var r in rows )
			{
				var e = list.Find( x => x.SteamId == r.SteamId );
				if ( e is null )
				{
					list.Add( new Entry { SteamId = r.SteamId, Name = r.Name, Mass = r.Mass } );
				}
				else if ( r.Mass > e.Mass )
				{
					e.Mass = r.Mass;
					e.Name = r.Name;
				}
			}

			list.Sort( ( x, y ) => y.Mass.CompareTo( x.Mass ) );
			if ( list.Count > 50 ) list.RemoveRange( 50, list.Count - 50 );

			Write( list );
		}
		catch ( Exception e )
		{
			Log.Info( $"[score] local board save failed: {e.Message}" );
		}
	}

	/// <summary> 本地榜 TopN(读不到/为空返回 null) </summary>
	public static List<( string Name, float Mass )> Top( int count )
	{
		try
		{
			var list = Load();
			if ( list.Count == 0 ) return null;

			list.Sort( ( x, y ) => y.Mass.CompareTo( x.Mass ) );

			var result = new List<( string, float )>();
			for ( int i = 0; i < list.Count && i < count; i++ )
				result.Add( ( list[i].Name, list[i].Mass ) );
			return result;
		}
		catch
		{
			return null;
		}
	}

	static List<Entry> Load()
	{
		var fs = FileSystem.Data;
		if ( fs is null ) return new List<Entry>();   // 早期可能为 null(实测坑)

		if ( !fs.FileExists( SavePath ) ) return new List<Entry>();
		return Json.Deserialize<List<Entry>>( fs.ReadAllText( SavePath ) ) ?? new List<Entry>();
	}

	static void Write( List<Entry> list )
	{
		var fs = FileSystem.Data;
		if ( fs is null ) return;
		fs.WriteAllText( SavePath, Json.Serialize( list ) );
	}
}