Stage 3: block reward 100, random halving pass-through, RZ800 14kJ, low-tier fakes, tier-mismatch always spotted
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c9dd900b16
commit
7255befa01
9 files changed
+80
-31
No files matched your search
@@ -2,6 +2,16 @@
|
||||
|
||||
## 未发布
|
||||
|
||||
### 第三阶段:数值调优(进行中)
|
||||
|
||||
- 混币器每轮手续费 6% → 4%;用币付款的顾客概率提到上层 30%、黑市 50%、革命派 50%、下层 25%,并多付 12%(溢价只加在币数上,污点仍按货值算)。
|
||||
- 区块奖励 90 → 100;RZ800 耗电 12 → 14 kJ。
|
||||
- 减半对币价的传导改成每次在 0.15–0.85 的指数里随机(锚 +11% 到 +80%),历次累计存档。
|
||||
- 低档次的卡也会被冒充:新增废片芯片 RZ10(不上架,只作假卡背后的真实芯片)。
|
||||
- 型号档次与实物不符(供电口、风扇数)的卡,摆给顾客时必被看穿并扣信誉(原来 50%),堵住用标签机把低档卡改成高档型号转卖的漏洞。
|
||||
- 买家看穿率、信誉扣减封顶收进 `Tuning.cs`。
|
||||
- 单元测试 8909 项通过;游戏内只验证了模组正常载入,没有跑到新的假卡、减半随机和币付款溢价的实际画面。
|
||||
|
||||
### 第二阶段(进行中)
|
||||
|
||||
- 开发者模式 F9 新增 `Run night (test)`、`Morning report (test)`、`Newspaper (test)`(走游戏自己的夜间流程并进入下一天,事件按天数过期)和 `Start crackdown (test)`,用来测夜间逻辑;`Tainted wallet (test)` 现在同时给 1000 CRT。
|
||||
|
||||
@@ -44,16 +44,22 @@ namespace Decentralized.Domain
|
||||
/// <summary>PSC 的参考价(信用点)。</summary>
|
||||
public double Price => Main.Price;
|
||||
|
||||
/// <summary>供给因子的目标:奖励比起始奖励每少一半,锚上移 2^传导指数;减半前 7 天内再逐日加预期。</summary>
|
||||
public static double SupplyTarget(int day, double reward)
|
||||
/// <summary>历次减半抽到的传导指数之和:价格锚的供给因子目标为 2^此值。</summary>
|
||||
public double HalvingLog;
|
||||
|
||||
/// <summary>减半当晚:抽本次的传导指数。</summary>
|
||||
public void OnHalving(Rng rng) => HalvingLog += Tuning.HalvingPassMin + rng.NextDouble() * (Tuning.HalvingPassMax - Tuning.HalvingPassMin);
|
||||
|
||||
/// <summary>供给因子的目标:已发生的减半各自抬高锚;减半前 7 天内再逐日加预期。</summary>
|
||||
public double SupplyTarget(int day)
|
||||
{
|
||||
double t = Math.Pow(Tuning.BlockReward / Math.Max(1e-9, reward), Tuning.HalvingPassThrough);
|
||||
double t = Math.Pow(2, HalvingLog);
|
||||
int toHalving = Tuning.HalvingDays - day % Tuning.HalvingDays; // 减半当晚 day % HalvingDays == 0,那天之后又是一个完整周期
|
||||
if (toHalving <= Tuning.HalvingRampDays) t *= 1 + Tuning.HalvingRamp * (Tuning.HalvingRampDays - toHalving + 1) / Tuning.HalvingRampDays;
|
||||
return t;
|
||||
}
|
||||
|
||||
public void UpdateSupply(int day, double reward) => Supply += (SupplyTarget(day, reward) - Supply) * Tuning.SupplyAdjust;
|
||||
public void UpdateSupply(int day) => Supply += (SupplyTarget(day) - Supply) * Tuning.SupplyAdjust;
|
||||
|
||||
public double Anchor(ICollection<MarketEvent> active)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Decentralized.Domain
|
||||
/// <summary>暴毙概率的品牌系数。</summary>
|
||||
/// <summary>档次:0 低端、1 中端、2 高端、3 旗舰(显卡)。实物特征(供电口、风扇数)只看档次。</summary>
|
||||
public int Tier;
|
||||
/// <summary>废片:只会作为假卡背后的真实芯片出现,不上架、不单独出现。低档次的卡也能被冒充靠它。</summary>
|
||||
public bool Scrap;
|
||||
public double DeathFactor;
|
||||
/// <summary>每天损耗的系数。</summary>
|
||||
public double WearFactor;
|
||||
@@ -39,7 +41,8 @@ namespace Decentralized.Domain
|
||||
Gpu("RZX60", Probidia, 0, 1.0, 4, 220), Gpu("RZX70", Probidia, 1, 5.0, 12, 800),
|
||||
Gpu("RZX80", Probidia, 2, 8.0, 18, 1300), Gpu("RZX90", Probidia, 3, 20.0, 30, 5000),
|
||||
Gpu("RZ70", Pmd, 0, 0.8, 2.5, 170), Gpu("RZ80", Pmd, 1, 4.0, 8, 680),
|
||||
Gpu("RZ800", Pmd, 2, 8.0, 12, 1500), Gpu("RZ900", Pmd, 3, 15.0, 20, 4000),
|
||||
Gpu("RZ800", Pmd, 2, 8.0, 14, 1500), Gpu("RZ900", Pmd, 3, 15.0, 20, 4000),
|
||||
Scrap("RZ10", Pmd, 0, 0.3, 2, 60),
|
||||
Asic("M1", 50, 60, 11000), Asic("M2", 120, 110, 27000), Asic("M3", 300, 220, 72000),
|
||||
}.ToDictionary(s => s.Model);
|
||||
|
||||
@@ -49,6 +52,13 @@ namespace Decentralized.Domain
|
||||
DeathFactor = brand == Probidia ? 0.7 : 1.0, WearFactor = brand == Probidia ? 1.0 : 1.2,
|
||||
};
|
||||
|
||||
private static HardwareSpec Scrap(string model, string brand, int tier, double h, double kj, int price)
|
||||
{
|
||||
var s = Gpu(model, brand, tier, h, kj, price);
|
||||
s.Scrap = true;
|
||||
return s;
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -10,10 +10,11 @@ namespace Decentralized.Domain
|
||||
|
||||
// 挖矿
|
||||
public const double PscStartPrice = 100;
|
||||
public const double BlockReward = 90;
|
||||
public const double BlockReward = 100;
|
||||
public const int HalvingDays = 90;
|
||||
/// <summary>减半对币价的传导:奖励减半时,价格锚上移 2^传导指数 倍(0.6 即约 +52%),市场已部分预期到,所以不是翻倍。</summary>
|
||||
public const double HalvingPassThrough = 0.6;
|
||||
/// <summary>减半对币价的传导:每次减半在区间内随机一个指数,价格锚上移 2^指数 倍(0.15–0.85 即约 +11% 到 +80%)。
|
||||
/// 参考比特币:每次减半后的涨幅差别很大,市场也已部分预期到,所以既不固定也不翻倍。</summary>
|
||||
public const double HalvingPassMin = 0.15, HalvingPassMax = 0.85;
|
||||
/// <summary>减半前一周的预期行情:锚逐日抬高,到减半当天为 +8%。</summary>
|
||||
public const int HalvingRampDays = 7;
|
||||
public const double HalvingRamp = 0.08;
|
||||
@@ -67,6 +68,9 @@ namespace Decentralized.Domain
|
||||
public const double CoinPayCrc = 0.30, CoinPayCrt = 0.50, CoinPayCra = 0.50, CoinPayPsc = 0.25;
|
||||
/// <summary>用币付款的溢价:顾客多付的比例,补偿持币的污点与兑出的手续费。</summary>
|
||||
public const double CoinPayPremium = 0.12;
|
||||
/// <summary>顾客验货:说法里每一项隐瞒被看穿的概率;型号档次不符(供电口、风扇数一眼可见)必被看穿;一次看穿最多扣的信誉。</summary>
|
||||
public const double CustomerSpotChance = 0.50, CustomerSpotTierMismatch = 1.0;
|
||||
public const int RepLossMax = 25;
|
||||
/// <summary>单个金融违规事件记入的罪证上限。</summary>
|
||||
public const double CrimeEventCap = 400;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Decentralized.Domain
|
||||
double share = kind == DealerKind.Scammer ? FakeShareScammer : kind == DealerKind.Middleman ? FakeShareMiddleman : 0;
|
||||
var label = pool[rng.Range(0, pool.Length - 1)];
|
||||
var card = new UsedCard { Model = label, Health = 25 + rng.NextDouble() * 60 };
|
||||
var cheaper = Hardware.Specs.Values.Where(s => !s.Asic && s.Price * 2 <= Hardware.Specs[label].Price).Select(s => s.Model).ToArray();
|
||||
var cheaper = Hardware.Specs.Values.Where(s => !s.Asic && s.Price * 2 <= Hardware.Specs[label].Price && (!s.Scrap || Hardware.Specs[label].Tier == 0)).Select(s => s.Model).ToArray();
|
||||
if (cheaper.Length > 0 && rng.Chance(share)) card.FakeOf = cheaper[rng.Range(0, cheaper.Length - 1)];
|
||||
card.MarkupPct = kind == DealerKind.Middleman ? (int)Math.Round(MiddlemanMarkup * 100) : 0;
|
||||
cards.Add(card);
|
||||
|
||||
@@ -283,7 +283,7 @@ namespace Decentralized
|
||||
var cur = Items.GetClaim(item) ?? Claim.Truth(u);
|
||||
if (row == CardLabels.RowSpec)
|
||||
{
|
||||
var models = Hardware.Specs.Values.Where(s => !s.Asic).Select(s => s.Model).ToList();
|
||||
var models = Hardware.Specs.Values.Where(s => !s.Asic && !s.Scrap).Select(s => s.Model).ToList();
|
||||
Items.EditClaim(item, row, 0, models[(models.IndexOf(cur.Model) + 1) % models.Count]);
|
||||
}
|
||||
else Items.EditClaim(item, row, row == CardLabels.RowSeal ? (cur.Get(row) == 0 ? 2 : 0) : row == CardLabels.RowBracket ? (cur.Get(row) + 1) % 2 : (cur.Get(row) + 1) % 3);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Decentralized
|
||||
dir.Add(Terminal, CreateTerminal);
|
||||
dir.Add(Rig, CreateRig);
|
||||
dir.Add(Guide, CreateGuide);
|
||||
foreach (var spec in Hardware.Specs.Values.Where(s => !s.Asic))
|
||||
foreach (var spec in Hardware.Specs.Values.Where(s => !s.Asic && !s.Scrap))
|
||||
{
|
||||
var s = spec;
|
||||
dir.Add(GpuId(s.Model), () => CreateGpu(s));
|
||||
@@ -123,14 +123,12 @@ namespace Decentralized
|
||||
|
||||
private static object checkedClient;
|
||||
private static readonly HashSet<int> checkedItems = new HashSet<int>();
|
||||
public const double CustomerSpotChance = 0.50;
|
||||
public const int RepLossMax = 25;
|
||||
|
||||
/// <summary>被顾客看穿时的信誉损失:骗的价差每满一周房租扣 1 点,最少 1,最多 25。</summary>
|
||||
public static int RepLoss(long gap) => (int)Math.Max(1, Math.Min(RepLossMax, Math.Round(gap / (double)Math.Max(1, PlayerStore.Instance.rentValue))));
|
||||
public static int RepLoss(long gap) => (int)Math.Max(1, Math.Min(Tuning.RepLossMax, Math.Round(gap / (double)Math.Max(1, PlayerStore.Instance.rentValue))));
|
||||
|
||||
/// <summary>
|
||||
/// 自己的卡摆给想买的顾客:说法里隐瞒的每一项,顾客有 50% 的概率看出来;看出来就拆穿这一项(价格回到真实情况),
|
||||
/// 自己的卡摆给想买的顾客:说法里隐瞒的每一项,顾客有 50% 的概率看出来(型号档次不符的必看出来,供电口和风扇数一眼可见);看出来就拆穿这一项(价格回到真实情况),
|
||||
/// 并且掉一点信誉。每位顾客对每张卡只看一次。说法比实际更差(把好卡说差了)顾客不会去指。
|
||||
/// </summary>
|
||||
public static void OfferForTest(GameItem item) { checkedItems.Clear(); OnOffered(item, true); }
|
||||
@@ -152,7 +150,7 @@ namespace Decentralized
|
||||
var rng = Session.Data.Rng;
|
||||
int found = 0;
|
||||
foreach (int r in CardLabels.Rows)
|
||||
if ((lies & r) != 0 && rng.Chance(CustomerSpotChance))
|
||||
if ((lies & r) != 0 && rng.Chance(r == CardLabels.RowSpec && claim.Value.Tier != Claim.Truth(u).Tier ? Tuning.CustomerSpotTierMismatch : Tuning.CustomerSpotChance))
|
||||
{
|
||||
found |= r;
|
||||
if (r != CardLabels.RowSpec && CardLabels.IsEvidence(r, claim.Value, Claim.Truth(u))) found |= CardLabels.RowSpec | CardLabels.RowBuild;
|
||||
|
||||
@@ -122,10 +122,11 @@ namespace Decentralized
|
||||
|
||||
double rewardBefore = d.Network.Reward;
|
||||
d.Network.Night(day, d.Market.Price, hash);
|
||||
if (d.Network.Reward < rewardBefore) d.Market.OnHalving(d.Rng);
|
||||
if (d.Network.Reward < rewardBefore) News.Show("pd_news_halving_" + day, L.T("news.halving.title"), L.T("news.halving.desc"), 2);
|
||||
double crcFactor = d.Stable.Night(day, d.Rng, 0);
|
||||
if (crcFactor < 1) d.Hot.Balances[Assets.CRC] = d.Hot.Get(Assets.CRC) * crcFactor;
|
||||
d.Market.UpdateSupply(day, d.Network.Reward);
|
||||
d.Market.UpdateSupply(day);
|
||||
if (Tuning.HalvingDays - day % Tuning.HalvingDays == 3) News.Show("pd_news_prehalving_" + day, L.T("news.prehalving.title"), L.T("news.prehalving.desc"), 3);
|
||||
d.Market.Night(events, d.Rng, day, Session.Price);
|
||||
report.Price = d.Market.Price;
|
||||
|
||||
@@ -89,11 +89,11 @@ internal static class Program
|
||||
Check(threw, "余额不足时拒绝");
|
||||
});
|
||||
|
||||
Test("挖矿:开局 1 H 每晚约 24.2 信用点;减半在第 90 天", () =>
|
||||
Test("挖矿:开局 1 H 每晚约 26.9 信用点;减半在第 90 天", () =>
|
||||
{
|
||||
var net = new Network();
|
||||
double perH = net.Mine(1) * Tuning.PscStartPrice;
|
||||
Check(Near(perH, 24.2, 0.2), $"1 H 每晚约 24.2 信用点(抽成后):{perH:0.00}");
|
||||
Check(Near(perH, 26.9, 0.2), $"1 H 每晚约 26.9 信用点(抽成后):{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);
|
||||
@@ -261,19 +261,28 @@ internal static class Program
|
||||
Check(w.AvgBuy("PSC") == null, "清仓后记录清除");
|
||||
});
|
||||
|
||||
Test("市场:减半抬高价格锚(逐日靠拢),减半前一周有预期行情", () =>
|
||||
Test("市场:减半抬高价格锚(随机幅度,逐日靠拢),减半前一周有预期行情", () =>
|
||||
{
|
||||
Check(Near(Market.SupplyTarget(10, Tuning.BlockReward), 1.0, 1e-9), "减半前很远:目标因子 1");
|
||||
double before = Market.SupplyTarget(89, Tuning.BlockReward), start = Market.SupplyTarget(83, Tuning.BlockReward);
|
||||
Check(start > 1.0 && before > start && before <= 1 + Tuning.HalvingRamp + 1e-9, $"减半前 7 天逐日抬高,最多 +8%:{start:0.000} → {before:0.000}");
|
||||
double after = Market.SupplyTarget(91, Tuning.BlockReward / 2);
|
||||
Check(Near(after, Math.Pow(2, Tuning.HalvingPassThrough), 1e-9), $"减半后目标因子 2^0.6 = {after:0.000}");
|
||||
var m = new Market();
|
||||
for (int i = 0; i < 7; i++) m.UpdateSupply(91 + i, Tuning.BlockReward / 2);
|
||||
Check(m.Supply > 1.0 && m.Supply < after, "逐晚靠拢目标,不一次跳到位");
|
||||
for (int i = 0; i < 40; i++) m.UpdateSupply(100 + i % 5, Tuning.BlockReward / 2);
|
||||
Check(Near(m.Supply, after, 0.02), "几十晚后基本到位");
|
||||
Check(m.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.4, "锚随之上移");
|
||||
Check(Near(m.SupplyTarget(10), 1.0, 1e-9), "减半前很远:目标因子 1");
|
||||
double before = m.SupplyTarget(89), start = m.SupplyTarget(83);
|
||||
Check(start > 1.0 && before > start && before <= 1 + Tuning.HalvingRamp + 1e-9, $"减半前 7 天逐日抬高,最多 +8%:{start:0.000} → {before:0.000}");
|
||||
double lo = 1e9, hi = 0;
|
||||
for (int seed = 1; seed <= 200; seed++)
|
||||
{
|
||||
var x = new Market(); x.OnHalving(new Rng((ulong)seed));
|
||||
lo = Math.Min(lo, x.HalvingLog); hi = Math.Max(hi, x.HalvingLog);
|
||||
}
|
||||
Check(lo >= Tuning.HalvingPassMin && hi <= Tuning.HalvingPassMax && hi - lo > 0.5, $"传导指数在区间内且确有变化:{lo:0.00}–{hi:0.00}");
|
||||
m.OnHalving(new Rng(7UL));
|
||||
double after = m.SupplyTarget(91);
|
||||
Check(Near(after, Math.Pow(2, m.HalvingLog), 1e-9), $"减半后目标因子 2^{m.HalvingLog:0.00} = {after:0.000}");
|
||||
var n = new Market { HalvingLog = m.HalvingLog };
|
||||
for (int i = 0; i < 7; i++) n.UpdateSupply(91 + i);
|
||||
Check(n.Supply > 1.0 && n.Supply < after, "逐晚靠拢目标,不一次跳到位");
|
||||
for (int i = 0; i < 40; i++) n.UpdateSupply(100 + i % 5);
|
||||
Check(Near(n.Supply, after, 0.02), "几十晚后基本到位");
|
||||
Check(n.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.05, "锚随之上移");
|
||||
});
|
||||
|
||||
Test("市场:其他交易对每晚被套利拉向参考价,三角价差收敛", () =>
|
||||
@@ -406,7 +415,18 @@ internal static class Program
|
||||
Test("序列号与供电风扇:四档实物特征;序列号按牌子和档次鉴定;指出供电风扇自动改回真实型号", () =>
|
||||
{
|
||||
Check(Hardware.FanCount.SequenceEqual(new[] { 1, 2, 3, 3 }) && Hardware.PowerPins.SequenceEqual(new[] { "6", "8", "8", "8+8" }), "低端 6 针单风扇、中端 8 针双风扇、高端 8 针三风扇、旗舰双 8 针三风扇");
|
||||
Check(Hardware.Specs.Values.Where(x => !x.Asic).All(x => Hardware.Specs.Values.Count(y => !y.Asic && y.Brand == x.Brand && y.Tier == x.Tier) == 1), "每个牌子每个档次只有一个型号");
|
||||
Check(Hardware.Specs.Values.Where(x => !x.Asic && !x.Scrap).All(x => Hardware.Specs.Values.Count(y => !y.Asic && !y.Scrap && y.Brand == x.Brand && y.Tier == x.Tier) == 1), "每个牌子每个档次只有一个型号(废片不算)");
|
||||
var rngF = new Rng(99UL); bool lowFake = false, midScrap = false;
|
||||
for (int i = 0; i < 400; i++)
|
||||
{
|
||||
foreach (var c in UsedDealers.Cards(DealerKind.Scammer, 60, rngF))
|
||||
{
|
||||
if (c.FakeOf == null) continue;
|
||||
if (Hardware.Specs[c.Model].Tier == 0 && Hardware.Specs[c.FakeOf].Scrap) lowFake = true;
|
||||
if (Hardware.Specs[c.Model].Tier > 0 && Hardware.Specs[c.FakeOf].Scrap) midScrap = true;
|
||||
}
|
||||
}
|
||||
Check(lowFake && !midScrap, "低档次的卡也会被冒充(背后是废片),中高档的假卡不用废片");
|
||||
var real = new Unit { Model = "RZ80", Health = 100, Look = new CardLook { Seed = 7 } };
|
||||
var t = CardLabels.SerialText(real);
|
||||
Check(t.StartsWith("MD2-") && CardLabels.Factories("PMD").Contains(int.Parse(t.Substring(4, 2))) && t.Length == 11, $"真卡的序列号:牌子 + 档次码 + 合法工厂码 + 四位流水:{t}");
|
||||
|
||||
Reference in new issue
Block a user