using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using UnityEngine; namespace HotUpdate.Gameplay { [Serializable] public sealed class CombatUnit { public int id, team, art, lane, z, hp, maxHp, shield, tier, slot, slow; public bool exited, spawned, boss; } [Serializable] public sealed class CombatTraceManifest { public int schema, protocol, logicVersion, rulesVersion, tickMs, lastTick, eventCount, initialDurability, durability, defeated; public string kind, traceId, traceHash, gameplayHash, skillsHash; public float duration; public bool won; public CombatUnit[] initial, final; public int[] leaks; } [Serializable] public sealed class CombatEvent { public string kind, skill, action, reason; public int seq, tick, source, target, cast, release, end, arrival, fromZ, toZ; public int id, team, art, lane, z, hp, maxHp, shield, tier, slot, slow, amount, absorbed, expires, factor, durability, defeated, leaks; public bool boss, spawned, exited, won; } [Serializable] public sealed class CombatPage { public string activityId, traceId, payload, sha256; public int cursor, nextCursor, total; } [Serializable] public sealed class CombatPageReply { public bool ok; public string error; public CombatPage page; } [Serializable] public sealed class CombatPageCommand { public string requestId, action="battle.events", activityId, traceId; public int revision, cursor, limit=256; } public static class CombatTraceValidation { public static string Hash(string text) {using(var hash=SHA256.Create())return BitConverter.ToString(hash.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-","").ToLowerInvariant();} static bool HashId(string value) => value!=null&&value.Length==64&&value.All(c=>c>='0'&&c<='9'||c>='a'&&c<='f'); public static bool Manifest(CombatTraceManifest m) { if(m==null||m.schema!=1||m.protocol!=1||m.logicVersion!=1||m.rulesVersion!=3||m.kind!="authoritative_battle"||m.tickMs!=50||m.lastTick<0||m.lastTick>=2400||m.eventCount<1||m.eventCount>50000||m.initialDurability<1||m.initialDurability>1000||!HashId(m.traceId)||!HashId(m.traceHash)||!HashId(m.gameplayHash)||!HashId(m.skillsHash)||m.initial==null||m.final?.Length!=m.initial.Length||m.initial.Length<10||m.initial.Length>15)return false; var ids=new HashSet(); foreach(var u in m.initial) if(u==null||!ids.Add(u.id)||u.maxHp<1||u.hp<0||u.hp>u.maxHp||u.lane<0||u.lane>2||u.z< -4000||u.z>11000||(u.team==0?(u.id!=u.art||u.art<0||u.art>9||u.slot<0||u.slot>5||u.tier<1||u.tier>2):(u.team!=1||u.id<100||u.id>108||u.art<0||u.art>4)))return false; return ids.Count(id=>id>=100)==9&&m.final.All(u=>u!=null&&ids.Contains(u.id))&&m.final.Select(u=>u.id).Distinct().Count()==ids.Count; } public static bool Events(CombatTraceManifest m,CombatEvent[] events) { if(!Manifest(m)||events==null||events.Length!=m.eventCount)return false; var ids=new HashSet(m.initial.Select(u=>u.id));int last=-1; var kinds=new HashSet{"spawn","move","cast_start","release","cast_end","cancel","projectile","hit","heal","shield","slow","shield_expired","slow_expired","death","exit","expired","battle_end"}; for(int i=0;im.lastTick||!kinds.Contains(e.kind))return false;last=e.tick; bool source=e.kind=="cast_start"||e.kind=="release"||e.kind=="cast_end"||e.kind=="cancel"||e.kind=="projectile"||e.kind=="hit"||e.kind=="heal"||e.kind=="shield"||e.kind=="slow"||e.kind=="death"||e.kind=="expired"; bool target=e.kind=="spawn"||e.kind=="move"||e.kind=="projectile"||e.kind=="hit"||e.kind=="heal"||e.kind=="shield"||e.kind=="slow"||e.kind=="shield_expired"||e.kind=="slow_expired"||e.kind=="death"||e.kind=="exit"||e.kind=="expired"; if(source&&!ids.Contains(e.source)||target&&!ids.Contains(e.target)||e.kind=="cast_start"&&(e.release