using System; using System.Linq; using Decentralized.Domain; // 纯逻辑单元测试(不需要游戏)。每个 Test 是一个场景,失败时打印原因,最后返回非零退出码。 // 涉及数值的检查以离线经济模拟的结果为对照。 internal static class Program { private static int _failed, _passed; private static void Check(bool ok, string what) { if (ok) _passed++; else { _failed++; Console.WriteLine(" ✗ " + what); } } private static void Test(string name, Action body) { Console.WriteLine("• " + name); try { body(); } catch (Exception ex) { _failed++; Console.WriteLine(" ✗ 异常: " + ex); } } private static bool Near(double a, double b, double tol) => Math.Abs(a - b) <= tol; private static int Main() { Test("随机数:同种子同序列;状态可复制后继续", () => { var a = new Rng(42); var b = new Rng(42); Check(Enumerable.Range(0, 100).All(_ => a.NextUInt() == b.NextUInt()), "同种子序列相同"); var c = new Rng { S0 = a.S0, S1 = a.S1, S2 = a.S2, S3 = a.S3 }; Check(Enumerable.Range(0, 100).All(_ => a.NextDouble() == c.NextDouble()), "复制状态后序列相同(存档读档)"); var r = new Rng(7); double mean = Enumerable.Range(0, 20000).Select(_ => r.NextDouble()).Average(); Check(Near(mean, 0.5, 0.01), $"均匀分布均值约 0.5:{mean:0.000}"); double g = Enumerable.Range(0, 20000).Select(_ => r.Gauss()).Select(x => x * x).Average(); Check(Near(g, 1.0, 0.05), $"正态分布方差约 1:{g:0.000}"); }); Test("资产登记:Ponero 不可追踪、不上合规交易所;稳定币挂钩", () => { Check(!Assets.Get(Assets.Ponero).Traceable && !Assets.Get(Assets.Ponero).Compliant, "Ponero"); Check(Assets.Get(Assets.CRC).Stable && Assets.Get(Assets.CRC).Freezable, "CRC 可冻结"); Check(Assets.Get(Assets.CRA).Stable && !Assets.Get(Assets.CRA).Freezable, "CRA 不可冻结"); Assets.Register(new AssetSpec { Id = "NPC_TEST", Issued = true }); Check(Assets.Get("NPC_TEST")?.Issued == true, "新增币种只是登记一条"); }); Test("交易对:恒定乘积、手续费、500 信用点约 2% 价格冲击", () => { var pool = Pool.Create(Venue.Dex, Assets.PSC, Assets.CRC, 100, 50000, Tuning.DexFee); Check(Near(pool.Price, 100, 1e-9), "初始价格 100"); double k = pool.BaseReserve * pool.QuoteReserve; Check(Near(pool.ImpactOfBuy(500), 0.02, 0.001), $"500 信用点买入的冲击约 2%:{pool.ImpactOfBuy(500):P2}"); double got = pool.BuyBase(500); Check(got > 4.9 && got < 4.97, $"500 信用点买到约 4.93 PSC:{got:0.000}"); Check(pool.BaseReserve * pool.QuoteReserve >= k, "手续费留在池里,k 不减少"); double back = pool.SellBase(got); Check(back < 500 && back > 496, $"马上卖回只损失手续费:{back:0.00}"); }); Test("交易对:夜间向目标价回归 15%(对数空间)并重设深度", () => { var pool = Pool.Create(Venue.Dex, Assets.PSC, Assets.CRC, 80, 50000, Tuning.DexFee); pool.Revert(100, Tuning.Revert, 60000); double expect = Math.Exp(Math.Log(80) + 0.15 * (Math.Log(100) - Math.Log(80))); Check(Near(pool.Price, expect, 1e-9), $"回归后价格 {pool.Price:0.00},期望 {expect:0.00}"); Check(Near(pool.QuoteReserve, 60000, 1e-9), "深度重设"); }); Test("地址:污点和成本按价值比例随币走;Ponero 换入时污点停止传播", () => { Func px = a => a == Assets.PSC ? 100 : 1; var a = new Wallet { Address = "A", Taint = 300, Basis = 1000 }; a.Add(Assets.CRT, 1000); var b = new Wallet { Address = "B" }; Wallet.Transfer(a, b, Assets.CRT, 250, px); Check(Near(b.Taint, 75, 1e-9) && Near(a.Taint, 225, 1e-9), $"转出 1/4,污点带走 1/4:{b.Taint}"); Check(Near(b.Basis, 250, 1e-9), "成本带走 1/4"); var c = new Wallet { Address = "C" }; b.Add(Assets.Ponero, 10); Wallet.Transfer(b, c, Assets.Ponero, 10, px); Check(c.Taint == 0, "Ponero 转入的地址不带污点"); bool threw = false; try { a.Add(Assets.CRT, -10000); } catch (InvalidOperationException) { threw = true; } Check(threw, "余额不足时拒绝"); }); Test("挖矿:开局 1 H 每晚约 13.4 信用点;减半在第 90 天", () => { var net = new Network(); double perH = net.Mine(1) * Tuning.PscStartPrice; Check(Near(perH, 13.4, 0.2), $"1 H 每晚约 13.4 信用点(抽成后):{perH:0.00}"); for (int d = 1; d <= 89; d++) net.Night(d, 100, 0); Check(net.Reward == Tuning.BlockReward, "第 89 天还没减半"); net.Night(90, 100, 0); Check(net.Reward == Tuning.BlockReward / 2, "第 90 天减半"); }); Test("挖矿:NPC 算力在有利可图时扩张,币价低时关机", () => { var up = new Network(); up.Night(1, 100, 0); Check(up.NpcHash > Tuning.NpcHash0, $"币价 100:扩张到 {up.NpcHash:0.0}"); var down = new Network(); down.Night(1, 30, 0); Check(down.NpcHash < Tuning.NpcHash0 && down.NpcHash >= Tuning.NpcHash0 * 0.95, $"币价 30:关机,最多 −5%:{down.NpcHash:0.0}"); }); Test("显卡:型号、显示单位、健康度与算力衰减", () => { Check(Hardware.Specs["RZX70"].DisplayHash == 50 && Hardware.Specs["RZ6800"].DisplayHash == 40, "RZX70 50 MH/s,RZ6800 40 MH/s"); Check(Near(Hardware.Specs["M3"].DisplayHash, 3, 1e-9), "RatDigg M3 3 GH/s"); var u = new Unit { Model = "RZX70" }; Check(u.Hash(false) == 5, "满健康 5 H"); u.Health = 25; Check(Near(u.Hash(false), 4.0, 1e-9), "健康 25:5 × (0.6 + 0.4 × 0.5) = 4 H"); u.Health = 0; Check(Near(u.Hash(false), 3.0, 1e-9), "健康 0:60%"); Check(Near(new Unit { Model = "RZX70" }.Hash(true), 6.25, 1e-9), "超频 +25%"); var fake = new Unit { Model = "RZX90", FakeOf = "RZX60" }; Check(fake.Hash(false) == 1.5 && fake.Label.Model == "RZX90", "假卡标着 RZX90,算力按 RZX60"); Check(Near(fake.DeathChance(false), new Unit { Model = "RZX90" }.DeathChance(false) * 2, 1e-12), "假卡暴毙率 ×2"); var r = new Unit { Model = "RZX70", Health = 50 }; Check(r.Refurbish() && r.Health == 80 && !r.Refurbish(), "翻新一次 +30,不能再翻新"); }); Test("显卡:累计暴毙概率与模拟器一致(第 50 / 100 / 150 天)", () => { double[] Run(string model, bool oc, int n) { var rng = new Rng(2026); var deadBy = new int[3]; for (int i = 0; i < n; i++) { var u = new Unit { Model = model }; for (int d = 1; d <= 150; d++) { u.Night(oc, rng); if (u.Dead) { if (d <= 50) deadBy[0]++; if (d <= 100) deadBy[1]++; deadBy[2]++; break; } } } return deadBy.Select(x => (double)x / n).ToArray(); } var p = Run("RZX70", false, 20000); Check(Near(p[0], 0.11, 0.02) && Near(p[1], 0.53, 0.02) && Near(p[2], 0.84, 0.02), $"Probidia 11% / 53% / 84%:{p[0]:P0} / {p[1]:P0} / {p[2]:P0}"); var q = Run("RZ6800", false, 20000); Check(Near(q[0], 0.20, 0.02) && Near(q[1], 0.76, 0.02) && Near(q[2], 0.95, 0.02), $"PMD 20% / 76% / 95%:{q[0]:P0} / {q[1]:P0} / {q[2]:P0}"); }); Console.WriteLine($"\n{_passed} 通过,{_failed} 失败"); return _failed == 0 ? 0 : 1; } }