LocalGameAuthority.cs
1 using System.Security.Cryptography; 2 using System.Text; 3 using System.Text.Json; 4 5 namespace GUNRPG.Application.Distributed; 6 7 /// <summary> 8 /// Single-node game authority that applies actions locally using the shared 9 /// <see cref="IDeterministicGameEngine"/>. Maintains the same action log, 10 /// state hashing, and interface as <see cref="DistributedAuthority"/> but 11 /// without any networking or peer replication. 12 /// </summary> 13 public sealed class LocalGameAuthority : IGameAuthority 14 { 15 private readonly IDeterministicGameEngine _engine; 16 private readonly List<DistributedActionEntry> _actionLog = new(); 17 private readonly object _lock = new(); 18 19 private GameStateDto _currentState; 20 private long _nextSequenceNumber; 21 private string _currentStateHash; 22 23 private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) 24 { 25 PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 26 WriteIndented = false 27 }; 28 29 public LocalGameAuthority(Guid nodeId, IDeterministicGameEngine engine) 30 { 31 NodeId = nodeId; 32 _engine = engine; 33 _currentState = new GameStateDto { ActionCount = 0, Operators = new List<GameStateDto.OperatorSnapshot>() }; 34 _currentStateHash = ComputeHash(_currentState); 35 } 36 37 public Guid NodeId { get; } 38 public bool IsDesynced => false; 39 40 public Task SubmitActionAsync(PlayerActionDto action, CancellationToken ct = default) 41 { 42 lock (_lock) 43 { 44 _currentState = _engine.Step(_currentState, action); 45 var hash = ComputeHash(_currentState); 46 _currentStateHash = hash; 47 48 _actionLog.Add(new DistributedActionEntry 49 { 50 SequenceNumber = _nextSequenceNumber++, 51 NodeId = NodeId, 52 Action = action, 53 StateHashAfterApply = hash 54 }); 55 } 56 57 return Task.CompletedTask; 58 } 59 60 public GameStateDto GetCurrentState() 61 { 62 lock (_lock) 63 { 64 return _currentState; 65 } 66 } 67 68 public string GetCurrentStateHash() 69 { 70 lock (_lock) 71 { 72 return _currentStateHash; 73 } 74 } 75 76 public IReadOnlyList<DistributedActionEntry> GetActionLog() 77 { 78 lock (_lock) 79 { 80 return _actionLog.ToList().AsReadOnly(); 81 } 82 } 83 84 private static string ComputeHash(GameStateDto state) 85 { 86 var json = JsonSerializer.Serialize(state, SerializerOptions); 87 var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(json)); 88 return Convert.ToHexString(bytes); 89 } 90 }