Stage 6.3: RatDigg ASIC miners, miner dealers on the black market, miner era event

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 00:58:14 +08:00
1 parent b249747855
commit 7d13ae2fe8
18 files changed
+395 -19

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
using System;
namespace Decentralized.Domain
{
/// <summary>
/// RatDigg 矿机暴毙后的修理:往机器的修理槽里按顺序放电子元件,约占机价的 4%。每一步只收这一步需要的那种元件,攒够这一步再进下一步,
/// 全部攒够就修好(健康扣 <see cref="Tuning.AsicRepairHealthCut"/> 点)。游戏里电子元件的价值是 33 / 350 / 2,500(普通 / 稀有 / 超科技)。
/// </summary>
public static class AsicRepair
{
public static (string Item, int Count)[] Steps(string model)
{
switch (model)
{
case "M1": return new[] { ("common_electronic", 4), ("rare_electronic", 1) };
case "M2": return new[] { ("rare_electronic", 2) };
case "M3": return new[] { ("hypertech_electronic", 1), ("rare_electronic", 1) };
default: throw new ArgumentOutOfRangeException(nameof(model));
}
}
/// <summary>当前这一步需要的元件;已修好(step 越界)返回 null。</summary>
public static (string Item, int Count)? Need(string model, int step)
{
var steps = Steps(model);
return step < steps.Length ? steps[step] : ((string, int)?)null;
}
/// <summary>
/// 投入 count 件 item:只收当前这一步需要的那种,攒够就进下一步。返回实际收下的件数;step 达到总步数表示修好了。
/// </summary>
public static int Insert(string model, ref int step, ref int progress, string item, int count)
{
var need = Need(model, step);
if (need == null || count <= 0 || item != need.Value.Item) return 0;
int take = Math.Min(count, need.Value.Count - progress);
progress += take;
if (progress >= need.Value.Count) { step++; progress = 0; }
return take;
}
public static bool Done(string model, int step) => step >= Steps(model).Length;
}
}
+26 -3
View File
@@ -6,7 +6,7 @@ namespace Decentralized.Domain
{
public enum PayMode { Escrow, Advance }
/// <summary>商家类型:矿卡(全新 / 二手)、矿架能源、模组、军火、枪匠配件、证件芯片、药物、化学、酿酒、水培、养殖、矿工、水务、仓储、工具、奢侈品;大宗商家、尖端店铺(只卖贵货,概率低)、个人卖家(什么都卖,价格随心所欲)随机出现。</summary>
public enum MerchantType { GpuNew, GpuUsed, Rigs, Modules, Arms, GunMods, Docs, Pharma, Chem, Brewer, Farm, Husbandry, Miner, Water, Storage, Tools, Luxury, Bulk, Elite, Personal }
public enum MerchantType { GpuNew, GpuUsed, Rigs, Modules, Arms, GunMods, Docs, Pharma, Chem, Brewer, Farm, Husbandry, Miner, Water, Storage, Tools, Luxury, Bulk, Elite, Personal, Asic }
public enum OrderState { Pending, Ready, Done, Refunded, Lost }
/// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary>
@@ -131,6 +131,7 @@ namespace Decentralized.Domain
private List<BmListing> MerchantGoods(int idx, BmSeller s, int day, int rep, Rng rng, Func<string, long> value)
{
var list = new List<BmListing>();
if (s.Type == MerchantType.Asic) return AsicGoods(idx, s, rep, rng);
bool gpuKind = s.Type == MerchantType.GpuNew || s.Type == MerchantType.GpuUsed;
int n = s.Type == MerchantType.Bulk ? 2 : s.Type == MerchantType.Elite ? Tuning.BmEliteListings : s.Type == MerchantType.Personal ? Tuning.BmPersonalListings : Tuning.BmMerchantListings;
List<BmEntry> entries = gpuKind ? new List<BmEntry>() : s.Type == MerchantType.Bulk || s.Type == MerchantType.Personal || s.Type == MerchantType.Elite ? AllEntries() : Merchants.Table[s.Type].ToList();
@@ -177,6 +178,27 @@ namespace Decentralized.Domain
return list;
}
public static int AsicRep(string model) => model == "M3" ? Tuning.AsicRepM3 : model == "M2" ? Tuning.AsicRepM2 : Tuning.AsicRepM1;
private static double AsicWeight(string model) => model == "M3" ? Tuning.AsicWeightM3 : model == "M2" ? Tuning.AsicWeightM2 : Tuning.AsicWeightM1;
/// <summary>矿机商:一台 RatDigg 矿机,型号按黑市声望放开,新机(70%)或二手;价格 = 机价(二手按健康度折算)× 商家系数。</summary>
private List<BmListing> AsicGoods(int idx, BmSeller s, int rep, Rng rng)
{
var models = new[] { "M1", "M2", "M3" }.Where(m => rep >= AsicRep(m)).ToList();
if (models.Count == 0) return new List<BmListing>();
double roll = rng.NextDouble() * models.Sum(AsicWeight);
string model = models.Last();
foreach (var m in models) { roll -= AsicWeight(m); if (roll <= 0) { model = m; break; } }
bool fresh = rng.Chance(Tuning.AsicNewShare);
double health = fresh ? 100 : rng.Range(Tuning.AsicUsedHealthMin, Tuning.AsicUsedHealthMax);
double factor = s.Factor * F(rng, 1 - Tuning.BmPriceJitter, 1 + Tuning.BmPriceJitter);
return new List<BmListing>
{
new BmListing { Seller = idx, Item = "gpu:" + model, Kind = GoodKind.Gpu, Level = 2, Model = model, Health = health, Qty = model == "M1" ? rng.Range(1, 2) : 1,
Price = (int)Math.Max(1, Math.Round(CardValue.Of(model, health) * factor)) },
};
}
/// <summary>每周重新生成商家、货架和论坛情报(与现货同一天刷新):从 6 种类型里抽 BmMerchantCount 家,另有概率出现一家大宗商家。</summary>
public void RefreshOrders(int day, int rep, Rng rng, Func<string, long> value, bool allTypes = false)
{
@@ -188,6 +210,7 @@ namespace Decentralized.Domain
{
if (rng.Chance(Tuning.BmBulkChance)) types.Add(MerchantType.Bulk);
if (rng.Chance(Tuning.BmEliteChance)) types.Add(MerchantType.Elite);
if (rep >= Tuning.AsicRepM1 && rng.Chance(Tuning.AsicChance)) types.Add(MerchantType.Asic);
for (int i = 0; i < Tuning.BmPersonalCount; i++) types.Add(MerchantType.Personal);
}
foreach (var t in types)
@@ -196,8 +219,8 @@ namespace Decentralized.Domain
bool scam = rng.Chance(personal ? Tuning.BmPersonalScam : Tuning.BmScamSellerShare);
// 骗子多半是新号,也有刷过好评的老号;个人卖家都是新号
bool fresh = personal || (scam ? rng.Chance(Tuning.BmScamFresh) : rng.Chance(Tuning.BmHonestFresh));
double lo = t == MerchantType.Elite ? Tuning.BmFactorEliteMin : t == MerchantType.GpuNew ? Tuning.BmFactorNewMin : t == MerchantType.GpuUsed ? Tuning.BmFactorUsedMin : t == MerchantType.Bulk ? Tuning.BmFactorBulkMin : Tuning.BmFactorMin;
double hi = t == MerchantType.Elite ? Tuning.BmFactorEliteMax : t == MerchantType.GpuNew ? Tuning.BmFactorNewMax : t == MerchantType.GpuUsed ? Tuning.BmFactorUsedMax : t == MerchantType.Bulk ? Tuning.BmFactorBulkMax : Tuning.BmFactorMax;
double lo = t == MerchantType.Elite || t == MerchantType.Asic ? Tuning.BmFactorEliteMin : t == MerchantType.GpuNew ? Tuning.BmFactorNewMin : t == MerchantType.GpuUsed ? Tuning.BmFactorUsedMin : t == MerchantType.Bulk ? Tuning.BmFactorBulkMin : Tuning.BmFactorMin;
double hi = t == MerchantType.Elite || t == MerchantType.Asic ? Tuning.BmFactorEliteMax : t == MerchantType.GpuNew ? Tuning.BmFactorNewMax : t == MerchantType.GpuUsed ? Tuning.BmFactorUsedMax : t == MerchantType.Bulk ? Tuning.BmFactorBulkMax : Tuning.BmFactorMax;
var s = new BmSeller
{
Name = ChainAddress.Make(rng.NextUInt()),
+24 -1
View File
@@ -152,6 +152,10 @@ namespace Decentralized.Domain
public sealed class Network
{
public double NpcHash = Tuning.NpcHash0;
/// <summary>NPC 矿工每 H 的耗电(kJ):矿机时代到来后减半,他们能挖得更久,全网算力均衡点升高。</summary>
public double NpcKj = Tuning.NpcKjPerH;
public int EraDay = -1, EraWarnDay = -1;
public bool EraWarned, EraDone;
public double Reward = Tuning.BlockReward;
/// <summary>停电、动荡等事件对 NPC 算力的可用比例。</summary>
public double Availability = 1.0;
@@ -164,6 +168,25 @@ namespace Decentralized.Domain
return Reward * playerHash / total * (1 - Tuning.PoolFee);
}
public enum EraStep { None, Warn, Start }
/// <summary>
/// "矿机时代":第一次调用时在 EraMin–EraMax 天之间随机定一天(至少留出预兆的天数),提前 4–7 天预兆,到点 NPC 耗电效率翻倍,永久。
/// 返回今晚发生的一步(预兆 / 开始)。
/// </summary>
public EraStep EraNight(int day, Rng rng)
{
if (EraDone) return EraStep.None;
if (EraDay < 0)
{
EraDay = Math.Max(day + Tuning.EraWarnMax + 1, rng.Range(Tuning.EraMin, Tuning.EraMax));
EraWarnDay = EraDay - rng.Range(Tuning.EraWarnMin, Tuning.EraWarnMax);
}
if (day >= EraDay) { NpcKj *= Tuning.EraKjMult; EraDone = true; return EraStep.Start; }
if (!EraWarned && day >= EraWarnDay) { EraWarned = true; return EraStep.Warn; }
return EraStep.None;
}
/// <summary>
/// 夜间:第 HalvingDays 天的倍数时减半;NPC 矿工按"每 H 收益 / 电费"与目标比的偏离扩张或关机,每晚最多 ±5%。
/// </summary>
@@ -172,7 +195,7 @@ namespace Decentralized.Domain
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 ratio = perH / (NpcKj * Tuning.ElecPrice);
double adj = Math.Max(-Tuning.NpcAdj, Math.Min(Tuning.NpcAdj, Tuning.NpcAdj * (ratio / Tuning.NpcTargetRatio - 1)));
NpcHash *= 1 + adj;
}
+12
View File
@@ -176,6 +176,18 @@ namespace Decentralized.Domain
public const int PerpPscLev = 5, PerpSparkLev = 3;
public const double PerpPscCap = 20000, PerpSparkCap = 5000;
/// <summary>RatDigg 矿机(ASIC):黑市矿机商每周出现的概率;M1 / M2 / M3 需要的黑市声望与出现权重;新机占比与二手的健康度范围。</summary>
public const double AsicChance = 0.25, AsicNewShare = 0.7;
public const int AsicRepM1 = 40, AsicRepM2 = 60, AsicRepM3 = 80;
public const double AsicWeightM1 = 1.0, AsicWeightM2 = 0.6, AsicWeightM3 = 0.4;
public const int AsicUsedHealthMin = 40, AsicUsedHealthMax = 85;
/// <summary>修好之后健康扣的点数;损坏的机器剩余价值占机价的比例。</summary>
public const int AsicRepairHealthCut = 20;
public const double AsicDeadValue = 0.15;
/// <summary>"矿机时代":第几天到第几天之间随机发生一次(永久),提前几天上报纸;NPC 矿工的耗电乘数(耗电效率翻倍 = 0.5)。</summary>
public const int EraMin = 50, EraMax = 90, EraWarnMin = 4, EraWarnMax = 7;
public const double EraKjMult = 0.5;
/// <summary>主交易对(信用点一侧)的深度随天数增长。</summary>
public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0);
}