From a76f04c75e51f1f6554b5335a8948c28faaca475 Mon Sep 17 00:00:00 2001 From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com> Date: Tue, 3 Nov 2026 21:44:19 +0800 Subject: [PATCH] Trap grouping and caps after economy simulation Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + src/Decentralized.Domain/BmOrders.cs | 23 +++++++++++++++------ src/Decentralized.Domain/BmSales.cs | 4 ++-- src/Decentralized.Domain/Tuning.cs | 2 ++ tests/Decentralized.Domain.Tests/Program.cs | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae7962..517d0a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - 订单改成商家货架(用户意见):黑市页"订单"改名"商家",每周从 6 种类型里抽 5 家(全新矿卡经销商、二手矿卡贩子、模组高科技、军火、证件芯片、药物),另有 25% 的周出现一家大宗商家(库存 10–25 件、价格系数 0.55–0.75、送货 6 天)。每家有自己的价格系数(订单价 = 基础价 × 系数,一般 0.70–0.95,全新卡 0.85–1.00,二手卡 0.65–0.90),每家 4 种货、库存 3–6 件。一次订 5 件以上 95 折、10 件以上 9 折。界面:件数按钮 ×1 / ×5 / ×10,商家按钮,所选商家的货架。药物(药片、梦尘、管制化学品)只在药物商家和大宗商家出现,也能转手出售。每周货架总值在游戏里抽样 60 周平均 13.2k(目标 1.4 万)。 - 商家类型扩到 18 种 + 大宗(用户意见,对照游戏的物品目录和玩家职业):全新矿卡经销商、二手矿卡贩子、矿架与能源、模组、军火(枪、手雷、炸药、刀具、电击枪)、枪匠配件(枪械配件、3D 打印芯片与模组、枪械零件)、证件芯片、药物(药片、注射器、血袋、手术工具)、化学品(化学用品、助熔剂、化学设备模组、量筒试剂瓶)、酿酒(酵母、荧光莓、啤酒、酒瓶)、水培农场(种子、水培模组、植物分析仪)、养殖(鱼卵、鱼子酱、循环水养殖系统、血清、屠宰机)、矿工(矿石、金属锭、电子元件、拾荒扫描仪)、水务(滤水、净水、储水、检测)、仓储(走私暗格、储藏区扩建、军用背包)、工具(涡轮增压器、等离子切割机、X 射线扫描仪、心智重编程器)、奢侈品(香水、精华液、香烟)。每周 7 家:2 家从矿卡 / 矿架 / 模组 4 类里抽(保证矿工路线总有货),其余 5 家从其余类型里抽;每家 3 种货,库存 2–5 件,单件基础价超过 4000 的不上。游戏里抽样 60 周货架总值平均 13.2k(目标 1.4 万)。药物改成并入药物商家,现货柜台的货不变。所有货都能转手出售。 - 尖端店铺与个人卖家(用户意见):贵货(单件基础价 ≥ 2000:超科技涡轮增压器、超科技储藏区、心智重编程器、顶级显卡等)不再被一刀切掉,改由"尖端店铺"卖:每周只有 15% 的概率出现,2 种货、库存 1–2 件、价格系数 0.85–1.00、送货 5 天;顶级全新显卡也在这里。个人卖家:每周固定 2 位,什么都卖(可能是药、模组、显卡、仍有 10% 混进贵货),每件价格系数各自随机 0.50–1.20(捡漏或被宰),库存 1–2 件,是骗子的概率 25%,都是新号所以只接受托管。常规商家仍只卖基础价 ≤ 4000 的货。 +- 追查调整(经济模拟后):同一晚到期的追查按来源合成一次事件,记入货款的 40%(`BmTrapShare`),同一次蜜罐 / 落网累计最多 400(`BmTrapWindowCap`);没收到货的订单(骗子退款、被截获、跑路、拒签、被查封)取消对应追查。原来每单各记一次、每晚上限 400,一个蜜罐窗口能连续记几千,模拟里一局 120 天几乎一直在罪证线以上。 - 手输收款地址(用户意见):Web3 里的付款与转账——黑市现货、商家下单、上架费、混币器存入、Ponero 兑换——点下去先出"确认付款"页,显示摘要和收款地址(商家是名字里的 0x… ,平台、混币器、Ponero 兑换处各有自己的随机地址,存档保存),要手输地址才放款,不分大小写,输错提示"地址不符,没有付款",可取消。DEX 兑换和银行存取不加(没有收款方);交易所的转账等阶段 5 多钱包时再接同一个确认页。游戏内验证:现货买超频模组,输错一位被拒、输对成交;Ponero 兑换处进入确认页。 - 商家名用随机哈希匿名,前面用方括号标类型(用户意见,试过每类型 10 个主题店名后撤回):如 "[全新矿卡] 0xdf9ec7";选中的商家按钮前有 "> "。 - 每周货架总值放宽到 2 万左右(用户意见):库存范围 2–5 → 2–6,游戏里抽样 60 周平均 22.2k,尖端店铺出现概率保持 15% 不变,不靠它拉高周转量。 diff --git a/src/Decentralized.Domain/BmOrders.cs b/src/Decentralized.Domain/BmOrders.cs index ba3cef7..147fc5b 100644 --- a/src/Decentralized.Domain/BmOrders.cs +++ b/src/Decentralized.Domain/BmOrders.cs @@ -63,6 +63,8 @@ namespace Decentralized.Domain public string Item; /// 对应的订单(没收到货的订单不会被追查);0 表示不挂订单(出售记录、落网)。 public int OrderId; + /// 同一次蜜罐 / 同一次落网的追查归为一组,累计记罪证有上限。 + public int Group; } /// 夜间发生的订单事件,供日志与新闻使用。 @@ -84,6 +86,9 @@ namespace Decentralized.Domain public int ClosedUntil = -1; /// 执法接管平台的最后一天(含);期间的交易之后会被追查。 public int HoneypotUntil = -1; + public int HoneypotStart = -1; + /// 每组追查已经记过的罪证。 + public Dictionary Charged = new Dictionary(); public List Traps = new List(); public int NextOrderId = 1; @@ -232,7 +237,7 @@ namespace Decentralized.Domain l.Qty -= count; if (l.Qty <= 0) Catalog.Remove(l); Orders.Add(o); - if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = credits, Why = "honeypot", Item = l.Item, OrderId = o.Id }); + if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = credits, Why = "honeypot", Item = l.Item, OrderId = o.Id, Group = HoneypotStart }); return o; } @@ -246,7 +251,7 @@ namespace Decentralized.Domain public List Night(int day, bool crackdown, Wallet w, Rng rng, int secRep = 0, Func price = null) { var ev = new List(); - if (!Honeypot(day) && rng.Chance(HoneypotChance(secRep))) { HoneypotUntil = day + Tuning.BmHoneypotDays; if (!Forum.Contains("easy")) Forum.Add("easy"); } + if (!Honeypot(day) && rng.Chance(HoneypotChance(secRep))) { HoneypotUntil = day + Tuning.BmHoneypotDays; HoneypotStart = day; if (!Forum.Contains("easy")) Forum.Add("easy"); } if (ClosedUntil == day) ev.Add(new BmEvent { Kind = "reopen" }); if (!Closed(day) && (Orders.Any(o => o.State == OrderState.Pending) || Sales.Any(x => x.State == SaleState.Listed || x.State == SaleState.Sold)) && rng.Chance(RugChance)) { @@ -289,14 +294,20 @@ namespace Decentralized.Domain { if (!rng.Chance(Tuning.BmSellerBust)) continue; foreach (var o in Orders.Where(x => x.State == OrderState.Done && x.Seller == name && day - x.DueDay <= 14)) - Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = o.Price, Why = "seller", Item = o.Item }); + Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = o.Price, Why = "seller", Item = o.Item, Group = -day }); Forum.Add("busted:" + name); } - foreach (var t in Traps.Where(x => day >= x.Day).ToList()) + // 同一晚到期的追查按来源(同一次蜜罐 / 落网)合成一次事件;记入货款的一部分,同一次累计有上限 + var due = Traps.Where(x => day >= x.Day).ToList(); + foreach (var g in due.GroupBy(x => new { x.Why, x.Group })) { - Traps.Remove(t); - ev.Add(new BmEvent { Kind = "trap", Amount = t.Amount, Order = new BmOrder { Item = t.Item, Seller = t.Why } }); + Charged.TryGetValue(g.Key.Group, out double done); + double amount = Math.Min(g.Sum(x => x.Amount) * Tuning.BmTrapShare, Tuning.BmTrapWindowCap - done); + if (amount < 1) continue; + Charged[g.Key.Group] = done + amount; + ev.Add(new BmEvent { Kind = "trap", Amount = amount, Order = new BmOrder { Item = g.First().Item, Seller = g.Key.Why } }); } + Traps.RemoveAll(x => day >= x.Day); Orders.RemoveAll(o => o.State != OrderState.Pending && o.State != OrderState.Ready && day - o.DueDay > 14); return ev; } diff --git a/src/Decentralized.Domain/BmSales.cs b/src/Decentralized.Domain/BmSales.cs index 42f363b..82efdf8 100644 --- a/src/Decentralized.Domain/BmSales.cs +++ b/src/Decentralized.Domain/BmSales.cs @@ -101,7 +101,7 @@ namespace Decentralized.Domain s.Taint = rng.Chance(Math.Min(1, TaintChance(s.Mult) + vb)) ? s.Proceeds * Math.Min(1, TaintRatio(s.Mult) + vb) : 0; s.PayDay = day + Tuning.BmSaleHold; Rating = Math.Min(100, Rating + 1); - if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "honeypot", Item = s.Item }); + if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "honeypot", Item = s.Item, Group = HoneypotStart }); ev.Add(new BmEvent { Kind = "sale_sold", Sale = s, Amount = s.Proceeds }); } foreach (var s in Sales.Where(x => x.State == SaleState.Sold && day >= x.PayDay).ToList()) @@ -118,7 +118,7 @@ namespace Decentralized.Domain var recent = Sales.Where(x => x.State == SaleState.Paid && day - x.PayDay <= 14).ToList(); if (recent.Count > 0 && rng.Chance(Tuning.BmSellerBust)) { - foreach (var s in recent) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "seller", Item = s.Item }); + foreach (var s in recent) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "seller", Item = s.Item, Group = -day }); Forum.Add("busted_buyer"); } Sales.RemoveAll(x => (x.State == SaleState.Paid || x.State == SaleState.Delisted || x.State == SaleState.Lost) && day - Math.Max(x.ListedDay, x.PayDay) > 14); diff --git a/src/Decentralized.Domain/Tuning.cs b/src/Decentralized.Domain/Tuning.cs index d9edb71..f9a3bb7 100644 --- a/src/Decentralized.Domain/Tuning.cs +++ b/src/Decentralized.Domain/Tuning.cs @@ -98,6 +98,8 @@ namespace Decentralized.Domain /// 订单被截获时变成受控交付的概率;每晚某个近期卖家落网的概率。 public const double BmHoneypotBase = 0.01, BmHoneypotPerSecRep = 0.0004, BmHoneypotMax = 0.06, BmControlled = 0.35, BmSellerBust = 0.008; public const int BmHoneypotDays = 6, BmTrapDelay = 4; + /// 追查记罪证:记入的比例(货款 × 比例)、同一次蜜罐(或一次落网)累计最多记多少。 + public const double BmTrapShare = 0.4, BmTrapWindowCap = 400; /// 货架上除违禁品外的两类货:高科技物品(需中档声望)和显卡的出现概率、加价(在现货溢价 / 订单价之外再加),显卡里夹假卡的比例、是全新卡(成色 100、封条完好、不会是假卡)的概率。 public const double BmTechChance = 0.30, BmGpuChance = 0.30, BmTechMarkup = 0.5, BmGpuMarkup = 0.2, BmGpuFake = 0.20, BmGpuNew = 0.15; /// 玩家上架出售:标价倍率范围(相对参考价)、上架费、成交抽成、成交后货款托管天数、假卡被买家看穿的概率; diff --git a/tests/Decentralized.Domain.Tests/Program.cs b/tests/Decentralized.Domain.Tests/Program.cs index 3cdacde..c6e41ce 100644 --- a/tests/Decentralized.Domain.Tests/Program.cs +++ b/tests/Decentralized.Domain.Tests/Program.cs @@ -630,7 +630,7 @@ internal static class Program var hw = new Wallet(); hw.Add(Assets.CRT, 5000); var ho = h.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 100 }, 1, PayMode.Escrow, hw, Assets.CRT, 10, price); Check(h.Traps.Count == 1 && h.Traps[0].Day == 10 + Tuning.BmTrapDelay, "蜜罐期间下单:记一笔追查"); - Check(h.Night(h.Traps[0].Day, false, hw, new Rng(3)).Any(e => e.Kind == "trap" && Near(e.Amount, ho.Price, 1e-9)) && h.Traps.Count == 0, "追查到期:产生罪证事件"); + Check(h.Night(h.Traps[0].Day, false, hw, new Rng(3)).Any(e => e.Kind == "trap" && Near(e.Amount, ho.Price * Tuning.BmTrapShare, 1e-9)) && h.Traps.Count == 0, "追查到期:产生罪证事件"); int ctrl = 0; for (int i = 0; i < 600; i++) {