Domain core: seeded RNG, asset registry, wallets, AMM pairs, mining
Pure logic layer with no game references: xoshiro128** RNG with savable state, asset registry (PSC, CRC, CRT, CRA, SPARK, Ponero), wallets whose taint and cost basis move with transfers, constant-product pools with nightly reversion, and mining (Probidia/PMD GPUs, RatDigg ASICs, network hash response, halving, wear and sudden death). 35 checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
ce3bed1467
14 files changed
+702
No files matched your search
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
public enum Venue { Exchange, Dex, Darknet }
|
||||
|
||||
/// <summary>
|
||||
/// 一个交易对 = 一个恒定乘积池(x·y = k)。Base 是被交易的币,Quote 是计价币;价格 = Quote 储备 / Base 储备。
|
||||
/// 每个交易对各有深度;夜间由 NPC 套利者把价格按比例拉向目标价。
|
||||
/// </summary>
|
||||
public sealed class Pool
|
||||
{
|
||||
public string Base, Quote;
|
||||
public Venue Venue;
|
||||
public double BaseReserve, QuoteReserve;
|
||||
public double Fee;
|
||||
|
||||
public string Id => $"{Venue}:{Base}/{Quote}";
|
||||
|
||||
public double Price => QuoteReserve / BaseReserve;
|
||||
|
||||
public static Pool Create(Venue venue, string baseAsset, string quote, double price, double quoteDepth, double fee) =>
|
||||
new Pool { Venue = venue, Base = baseAsset, Quote = quote, QuoteReserve = quoteDepth, BaseReserve = quoteDepth / price, Fee = fee };
|
||||
|
||||
/// <summary>预估:卖出 baseIn 个 Base 能得到多少 Quote(不改池子)。</summary>
|
||||
public double QuoteOut(double baseIn)
|
||||
{
|
||||
double x = BaseReserve + baseIn * (1 - Fee);
|
||||
return QuoteReserve - BaseReserve * QuoteReserve / x;
|
||||
}
|
||||
|
||||
/// <summary>预估:花 quoteIn 个 Quote 能买到多少 Base(不改池子)。</summary>
|
||||
public double BaseOut(double quoteIn)
|
||||
{
|
||||
double y = QuoteReserve + quoteIn * (1 - Fee);
|
||||
return BaseReserve - BaseReserve * QuoteReserve / y;
|
||||
}
|
||||
|
||||
/// <summary>卖出 Base,返回得到的 Quote。手续费留在池里。</summary>
|
||||
public double SellBase(double baseIn)
|
||||
{
|
||||
if (baseIn <= 0) return 0;
|
||||
double out_ = QuoteOut(baseIn);
|
||||
BaseReserve += baseIn;
|
||||
QuoteReserve -= out_;
|
||||
return out_;
|
||||
}
|
||||
|
||||
/// <summary>用 Quote 买入 Base,返回得到的 Base。</summary>
|
||||
public double BuyBase(double quoteIn)
|
||||
{
|
||||
if (quoteIn <= 0) return 0;
|
||||
double out_ = BaseOut(quoteIn);
|
||||
QuoteReserve += quoteIn;
|
||||
BaseReserve -= out_;
|
||||
return out_;
|
||||
}
|
||||
|
||||
/// <summary>一笔交易的价格冲击(交易后价格相对交易前的变化比例)。</summary>
|
||||
public double ImpactOfBuy(double quoteIn)
|
||||
{
|
||||
double before = Price;
|
||||
double y = QuoteReserve + quoteIn * (1 - Fee);
|
||||
double x = BaseReserve * QuoteReserve / y;
|
||||
return (y / x) / before - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 夜间:价格在对数空间里向 target 靠拢 rate 的比例,同时把 Quote 一侧的深度重设为 quoteDepth。
|
||||
/// </summary>
|
||||
public void Revert(double target, double rate, double quoteDepth)
|
||||
{
|
||||
double lp = Math.Log(Price);
|
||||
lp += rate * (Math.Log(target) - lp);
|
||||
SetPrice(Math.Exp(lp), quoteDepth);
|
||||
}
|
||||
|
||||
public void SetPrice(double price, double quoteDepth)
|
||||
{
|
||||
QuoteReserve = quoteDepth;
|
||||
BaseReserve = quoteDepth / price;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>资产的性质。新增币种只是往 <see cref="Assets"/> 里加一条。</summary>
|
||||
public sealed class AssetSpec
|
||||
{
|
||||
public string Id;
|
||||
/// <summary>挂钩 1 信用点的稳定币。</summary>
|
||||
public bool Stable;
|
||||
/// <summary>发行方可以冻结地址(CRC;CRT 只在镇压期间)。</summary>
|
||||
public bool Freezable;
|
||||
/// <summary>链上可追踪:污点随币传播。Ponero 为 false,换入时污点停止传播。</summary>
|
||||
public bool Traceable = true;
|
||||
/// <summary>合规交易所可以上架。</summary>
|
||||
public bool Compliant = true;
|
||||
/// <summary>程序生成或玩家发行的代币。</summary>
|
||||
public bool Issued;
|
||||
}
|
||||
|
||||
/// <summary>资产登记。信用点(游戏现金)不在链上,不登记。</summary>
|
||||
public static class Assets
|
||||
{
|
||||
public const string PSC = "PSC";
|
||||
public const string CRC = "CRC";
|
||||
public const string CRT = "CRT";
|
||||
public const string CRA = "CRA";
|
||||
public const string SPARK = "SPARK";
|
||||
public const string Ponero = "PONERO";
|
||||
|
||||
private static readonly Dictionary<string, AssetSpec> Table = new Dictionary<string, AssetSpec>();
|
||||
|
||||
static Assets()
|
||||
{
|
||||
Register(new AssetSpec { Id = PSC });
|
||||
Register(new AssetSpec { Id = CRC, Stable = true, Freezable = true });
|
||||
Register(new AssetSpec { Id = CRT, Stable = true, Freezable = true });
|
||||
Register(new AssetSpec { Id = CRA, Stable = true });
|
||||
Register(new AssetSpec { Id = SPARK });
|
||||
Register(new AssetSpec { Id = Ponero, Traceable = false, Compliant = false });
|
||||
}
|
||||
|
||||
public static void Register(AssetSpec spec) => Table[spec.Id] = spec;
|
||||
|
||||
public static bool TryGet(string id, out AssetSpec spec) => Table.TryGetValue(id, out spec);
|
||||
|
||||
public static AssetSpec Get(string id) => Table.TryGetValue(id, out var s) ? s : null;
|
||||
|
||||
public static IEnumerable<AssetSpec> All => Table.Values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- 纯逻辑:资产与账本、AMM 交易对、市场、挖矿。不引用 Unity / 游戏程序集。
|
||||
目标 netstandard2.1:游戏是 Unity Mono。JSON 用游戏自带的 Newtonsoft.Json 13(编译时引用 NuGet 包,运行时用游戏的那份,不随 mod 发布)。 -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<AssemblyName>ProbablyDecentralized.Domain</AssemblyName>
|
||||
<RootNamespace>Decentralized.Domain</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>显卡或矿机的型号。算力内部单位 H(1 H = 10 MH/s),耗电单位 kJ/天。</summary>
|
||||
public sealed class HardwareSpec
|
||||
{
|
||||
public string Model;
|
||||
public string Brand;
|
||||
public double Hash;
|
||||
public double Kj;
|
||||
public int Price;
|
||||
/// <summary>暴毙概率的品牌系数。</summary>
|
||||
public double DeathFactor;
|
||||
/// <summary>每天损耗的系数。</summary>
|
||||
public double WearFactor;
|
||||
/// <summary>矿机(ASIC):独立机器;false 为插在矿架上的显卡。</summary>
|
||||
public bool Asic;
|
||||
|
||||
/// <summary>显示用算力:显卡 MH/s,矿机 GH/s。</summary>
|
||||
public double DisplayHash => Asic ? Hash * 10 / 1000 : Hash * 10;
|
||||
}
|
||||
|
||||
public static class Hardware
|
||||
{
|
||||
public const string Probidia = "Probidia", Pmd = "PMD", RatDigg = "RatDigg";
|
||||
|
||||
public static readonly IReadOnlyDictionary<string, HardwareSpec> Specs = new[]
|
||||
{
|
||||
Gpu("RZX50", Probidia, 1.0, 4, 120), Gpu("RZX60", Probidia, 1.5, 5, 180), Gpu("RZX70", Probidia, 5.0, 12, 450),
|
||||
Gpu("RZX80Ti", Probidia, 8.0, 18, 750), Gpu("RZX90", Probidia, 20.0, 30, 2800),
|
||||
Gpu("RZ470", Pmd, 0.8, 2.5, 90), Gpu("RZ580", Pmd, 1.2, 3, 140), Gpu("RZ6800", Pmd, 4.0, 8, 380),
|
||||
Gpu("RZ7900", Pmd, 15.0, 20, 2200),
|
||||
Asic("M1", 50, 60, 6000), Asic("M2", 120, 110, 15000), Asic("M3", 300, 220, 40000),
|
||||
}.ToDictionary(s => s.Model);
|
||||
|
||||
private static HardwareSpec Gpu(string model, string brand, double h, double kj, int price) => new HardwareSpec
|
||||
{
|
||||
Model = model, Brand = brand, Hash = h, Kj = kj, Price = price,
|
||||
DeathFactor = brand == Probidia ? 0.7 : 1.0, WearFactor = brand == Probidia ? 1.0 : 1.2,
|
||||
};
|
||||
|
||||
private static HardwareSpec Asic(string model, double h, double kj, int price) => new HardwareSpec
|
||||
{
|
||||
Model = model, Brand = RatDigg, Hash = h, Kj = kj, Price = price, DeathFactor = 0.5, WearFactor = 0.8, Asic = true,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>一张显卡或一台矿机的实例状态(存进物品标签)。</summary>
|
||||
public sealed class Unit
|
||||
{
|
||||
public string Model;
|
||||
/// <summary>假卡:标着 Model,芯片实为 FakeOf。</summary>
|
||||
public string FakeOf;
|
||||
public double Health = 100;
|
||||
public bool Dead;
|
||||
public bool Refurbished;
|
||||
|
||||
public HardwareSpec Label => Hardware.Specs[Model];
|
||||
public HardwareSpec Chip => Hardware.Specs[FakeOf ?? Model];
|
||||
|
||||
/// <summary>当前算力:健康 ≥ 50 时满算力,低于 50 线性降到健康 0 时的 60%。</summary>
|
||||
public double Hash(bool overclock)
|
||||
{
|
||||
if (Dead) return 0;
|
||||
double h = Chip.Hash * (Health >= 50 ? 1.0 : 0.6 + 0.4 * Health / 50);
|
||||
return overclock ? h * Tuning.OcHash : h;
|
||||
}
|
||||
|
||||
public double Kj(bool overclock) => Dead ? 0 : Chip.Kj * (overclock ? Tuning.OcKj : 1);
|
||||
|
||||
/// <summary>今晚的暴毙概率。</summary>
|
||||
public double DeathChance(bool overclock)
|
||||
{
|
||||
double p = Tuning.DeathBase + Tuning.DeathWorn * Math.Pow(1 - Health / 100, 2);
|
||||
p *= Label.DeathFactor * (FakeOf != null ? Tuning.FakeDeathFactor : 1) * (overclock ? Tuning.OcDeath : 1);
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>一夜的损耗与暴毙判定;返回是否在今晚暴毙。</summary>
|
||||
public bool Night(bool overclock, Rng rng)
|
||||
{
|
||||
if (Dead) return false;
|
||||
Health = Math.Max(0, Health - Label.WearFactor * (overclock ? Tuning.OcWear : 1));
|
||||
if (rng.Chance(DeathChance(overclock)))
|
||||
{
|
||||
Dead = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>翻新:每张卡一次,健康 +30。</summary>
|
||||
public bool Refurbish()
|
||||
{
|
||||
if (Dead || Refurbished) return false;
|
||||
Refurbished = true;
|
||||
Health = Math.Min(100, Health + Tuning.RefurbishHealth);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>全网:NPC 背景算力、出块奖励与减半。</summary>
|
||||
public sealed class Network
|
||||
{
|
||||
public double NpcHash = Tuning.NpcHash0;
|
||||
public double Reward = Tuning.BlockReward;
|
||||
/// <summary>停电、动荡等事件对 NPC 算力的可用比例。</summary>
|
||||
public double Availability = 1.0;
|
||||
|
||||
/// <summary>按算力分得今晚的出块(已扣矿池抽成)。</summary>
|
||||
public double Mine(double playerHash)
|
||||
{
|
||||
if (playerHash <= 0) return 0;
|
||||
double total = NpcHash * Availability + playerHash;
|
||||
return Reward * playerHash / total * (1 - Tuning.PoolFee);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 夜间:第 HalvingDays 天的倍数时减半;NPC 矿工按"每 H 收益 / 电费"与目标比的偏离扩张或关机,每晚最多 ±5%。
|
||||
/// </summary>
|
||||
public void Night(int day, double price, double playerHash)
|
||||
{
|
||||
if (day > 0 && day % Tuning.HalvingDays == 0) Reward /= 2;
|
||||
double total = NpcHash * Availability + playerHash;
|
||||
double perH = Reward * price / total;
|
||||
double ratio = perH / (Tuning.NpcKjPerH * Tuning.ElecPrice);
|
||||
double adj = Math.Max(-Tuning.NpcAdj, Math.Min(Tuning.NpcAdj, Tuning.NpcAdj * (ratio / Tuning.NpcTargetRatio - 1)));
|
||||
NpcHash *= 1 + adj;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// 带种子、状态可存档的随机数(xoshiro128**)。游戏自己的 RNG 不带种子;mod 的事件、行情、暴毙都用这个,
|
||||
/// 状态随存档保存,读档后结果不变,测试也能复现。
|
||||
/// </summary>
|
||||
public sealed class Rng
|
||||
{
|
||||
public uint S0, S1, S2, S3;
|
||||
|
||||
public Rng() : this(1) { }
|
||||
|
||||
public Rng(ulong seed)
|
||||
{
|
||||
// SplitMix64 展开种子
|
||||
ulong z = seed;
|
||||
ulong Next()
|
||||
{
|
||||
z += 0x9E3779B97F4A7C15UL;
|
||||
ulong x = z;
|
||||
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9UL;
|
||||
x = (x ^ (x >> 27)) * 0x94D049BB133111EBUL;
|
||||
return x ^ (x >> 31);
|
||||
}
|
||||
ulong a = Next(), b = Next();
|
||||
S0 = (uint)a; S1 = (uint)(a >> 32); S2 = (uint)b; S3 = (uint)(b >> 32);
|
||||
if ((S0 | S1 | S2 | S3) == 0) S0 = 1;
|
||||
}
|
||||
|
||||
private static uint Rotl(uint x, int k) => (x << k) | (x >> (32 - k));
|
||||
|
||||
public uint NextUInt()
|
||||
{
|
||||
uint result = Rotl(S1 * 5, 7) * 9;
|
||||
uint t = S1 << 9;
|
||||
S2 ^= S0; S3 ^= S1; S1 ^= S2; S0 ^= S3;
|
||||
S2 ^= t;
|
||||
S3 = Rotl(S3, 11);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>[0, 1)</summary>
|
||||
public double NextDouble() => (NextUInt() >> 8) * (1.0 / (1 << 24));
|
||||
|
||||
public bool Chance(double p) => NextDouble() < p;
|
||||
|
||||
/// <summary>[min, max],含两端。</summary>
|
||||
public int Range(int min, int max) => min + (int)(NextDouble() * (max - min + 1));
|
||||
|
||||
public double Range(double min, double max) => min + NextDouble() * (max - min);
|
||||
|
||||
/// <summary>标准正态(Box–Muller)。</summary>
|
||||
public double Gauss()
|
||||
{
|
||||
double u1 = 1.0 - NextDouble(), u2 = NextDouble();
|
||||
return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Cos(2.0 * Math.PI * u2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// 数值(普通难度基准)。改数值只改这里。
|
||||
/// </summary>
|
||||
public static class Tuning
|
||||
{
|
||||
// 挖矿
|
||||
public const double PscStartPrice = 100;
|
||||
public const double BlockReward = 50;
|
||||
public const int HalvingDays = 90;
|
||||
public const double NpcHash0 = 360;
|
||||
public const double NpcKjPerH = 2.4;
|
||||
public const double ElecPrice = 1.5;
|
||||
public const double NpcTargetRatio = 3.5;
|
||||
public const double NpcAdj = 0.05;
|
||||
public const double PoolFee = 0.03;
|
||||
public const int RigPrice = 400, RigKj = 2, RigSlots = 4;
|
||||
public const double OcHash = 1.25, OcKj = 1.40, OcWear = 1.3, OcDeath = 1.2;
|
||||
public const double DeathBase = 0.001, DeathWorn = 0.03, FakeDeathFactor = 2;
|
||||
public const double RefurbishHealth = 30;
|
||||
|
||||
// 市场
|
||||
public const double PoolDepth0 = 50000;
|
||||
public const double DexFee = 0.003, ExchangeFee = 0.002;
|
||||
public const double Revert = 0.15;
|
||||
public const double PscVol = 0.04;
|
||||
public const double SentimentKappa = 0.02, SentimentVol = 0.02;
|
||||
public const double PriceFloor = 0.3, PriceCeiling = 3.0;
|
||||
|
||||
/// <summary>主交易对(信用点一侧)的深度随天数增长。</summary>
|
||||
public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个链上地址:各币种余额,加上整个地址的污点额与成本(信用点等值)。
|
||||
/// 污点和成本按转出的价值比例随币走。
|
||||
/// </summary>
|
||||
public sealed class Wallet
|
||||
{
|
||||
public string Address;
|
||||
public Dictionary<string, double> Balances = new Dictionary<string, double>();
|
||||
/// <summary>污点额(信用点等值)。</summary>
|
||||
public double Taint;
|
||||
/// <summary>成本(信用点):出金时只对超出成本的部分征税。</summary>
|
||||
public double Basis;
|
||||
/// <summary>被执法标记。</summary>
|
||||
public bool Flagged;
|
||||
|
||||
public double Get(string asset) => Balances.TryGetValue(asset, out var v) ? v : 0;
|
||||
|
||||
public void Add(string asset, double amount)
|
||||
{
|
||||
if (amount == 0) return;
|
||||
double v = Get(asset) + amount;
|
||||
if (v < -1e-9) throw new InvalidOperationException($"{Address}: {asset} 余额不足");
|
||||
if (Math.Abs(v) < 1e-12) Balances.Remove(asset);
|
||||
else Balances[asset] = v;
|
||||
}
|
||||
|
||||
/// <summary>按价格表估值(信用点)。</summary>
|
||||
public double Value(Func<string, double> price) => Balances.Sum(kv => kv.Value * price(kv.Key));
|
||||
|
||||
/// <summary>
|
||||
/// 转出:taint 和 basis 按转出价值占地址总价值的比例带走。Ponero 等不可追踪资产转入时,污点不随之进入目标地址。
|
||||
/// </summary>
|
||||
public static void Transfer(Wallet from, Wallet to, string asset, double amount, Func<string, double> price)
|
||||
{
|
||||
if (amount <= 0) return;
|
||||
double total = from.Value(price);
|
||||
double share = total > 0 ? Math.Min(1.0, amount * price(asset) / total) : 0;
|
||||
double taint = from.Taint * share, basis = from.Basis * share;
|
||||
from.Add(asset, -amount);
|
||||
from.Taint -= taint;
|
||||
from.Basis -= basis;
|
||||
to.Add(asset, amount);
|
||||
var spec = Assets.Get(asset);
|
||||
if (spec == null || spec.Traceable) to.Taint += taint;
|
||||
to.Basis += basis;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user