diff --git a/CHANGELOG.md b/CHANGELOG.md
index 036ab4b..569c8cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,11 +7,21 @@
- Web3 页"预测"子页(游戏第 8 天起,Web3 治理社区已买下):每个盘口是一道是非题,开盘当天到窗口开始前可以买卖,窗口最后一天夜里结算;每天开门后补盘,同时开放 3–6 个,每天最多新开 2 个。资金走链上钱包的 CRC:买入、全部卖出都是合约调用,要在钱包上签名(和质押一样;买入的手续费在核对页选档、用 CRC 付,卖出用价值最高的币付);持仓记在签名的钱包名下,结算时兑付回这个钱包,同一盘口各钱包只能押同一边,持仓上限和内幕判定按盘口合计(拆到几个钱包躲不过)。污点与成本按花掉的价值比例随资金进持仓,赢的钱继承押注资金的污点比例,卖回时按比例退。
- 盘口类型:新闻事件("出现某常规事件",先验 = 游戏掷骰规则:5 天后每天 50 / 35 / 25% 有事件,再按权重抽)、派系行动("某派系有行动开始",每天开始概率 6%,估计值)、水纯度(明天上层 / 下层水纯度 ≥ 阈值,先验精确)、PSC 价格(窗口最后一天夜间结算前的价格 ≥ 阈值,先验用价格过程的正态近似)。事件和派系行动靠夜间对比"昨晚生效的事件"得到当天新出现的;窗口里缺任何一天的记录(没睡觉结算)盘口作废、退回本金。
- 界面不再出现"份":持仓直接写成本和"押中得多少"(每份押中兑付 1 信用点),盘口列表、右栏的各钱包持仓和买入预览都是这种写法,右栏另给"现在卖出得多少"。
+- 玩法打磨:
+ - 界面:盘口行和右栏显示每份现价(是 / 否,等于概率)、持仓份数、均价、成本、押中得多少、现在卖出得多少;买入预览带均价。
+ - 风险放大:单盘最大亏损改为 800 + 25 × 天数(第 30 天 1550),押错本金全亏;盘口价格每晚除了向合理概率靠拢还带随机噪声;买卖各 2%。
+ - 消息反应:每晚还没封盘的盘口向"公开信息下的合理概率"靠拢一半(报纸上已排期的事件 / 派系行动 → 92%,进行中或冷却中 → 2%,行情盘按最新价格,gas 盘按周期,CRA 盘按补贴池水位,矿机时代按预警),再加 logit 噪声 0.35。价格在夜里才反应,白天看到消息的人可以抢在前面买、涨价后卖,不必持有到结算;开盘到窗口开始的天数从 1–2 天改为 1–3 天,让反应有时间发生。
+ - 线索栏:盘口面板列出和它有关的已知信息:排期(传闻、派系特权)、事件进行中或冷却、派系行动进行中、减半日与已排期的镇压 / 停电、价格锚、近几天 gas、CRA 补贴池水位和每晚消耗、矿机时代预告、手里的内幕消息。
+ - 新盘口:gas 指数(明天 ≥ 阈值,7 天周期,先验是七个相位的平均,周期是玩家看得出来的);CRA 预警(补贴池每晚被消耗,归零必预警,质押越多越快);矿机时代(预告前按 50–90 天均匀,预告后落在 4–7 天里,要 day + 4 ≥ 50 才开,避免泄露)。CRA 和矿机时代读 mod 状态,所以预测结算挪到夜间结算的最后(行情盘、gas 盘用结算开始时记下的收盘数据)。
+ - 玩家能推动的:PSC 价格盘能被自己的大单推动(操纵检出放在 7.2);CRA 盘靠质押量推动补贴池消耗。
+ - 情报页(Web3 黑市解锁且声望 ≥ 20 才出现):局势简报(120 CRT)列出五个势力值和每个派系按游戏阈值现在能发起的行动,派系行动已排期 / 进行中的也标出,10% 概率是假情报(数值是编的);内幕消息只卖已经定了的盘口(排进日程的派系行动、矿机时代日子),三档准确率约 60 / 80 / 95%,价格是盘口最大亏损的 1 / 2.5 / 5%(CRT),一成是假情报(说的和真相相反);买了消息再押同一边算内幕单。付款是 CRT 的合约调用,要签名,花掉的币带走对应比例的污点。
+ - 复核(predsim v2):不知情随机押仍是负期望;低先验盘口买消息基本亏;中先验盘口(20–50%)高档消息下注 400 以上才赚,低档消息几乎都是亏的陷阱;单盘输赢在上千,亏钱的概率可观。
- 定价:LMSR,初始价 = 先验,单盘系统最大亏损 = 300 + 10 × 天数,流动性参数由它反推;买卖各收 2%;单人单盘份额上限 = 最大亏损 × 3;一个盘口只持有一边。
- 内幕:下注时这个事件(或派系行动)已经以"玩家看得到"的形式排进日程(传闻纸条、派系特权),而且落在窗口里,押「是」算内幕单。内幕持仓达到份额上限的 40% 后,结算时 50% 概率被查:只退本金,收益的一半(上限 400)记 FRAUD 罪证。情报顾客没接。
- 复核(ops `research/predsim`):不知情随机押期望为负;谨慎的内幕(压在查处线下)在低先验盘口一盘净赚 270–520(第 30 天最大亏损 600),买满上限的期望反而更低。行情先验和真实 `Market` 过程的蒙特卡洛对得上(单元测试,误差 < 4%)。
- 游戏内验证:新开盘口列表显示问题、先验、持仓;在 PLEDGER 上签名买入 100 CRC 得 117–424 份并涨价(钱包 CRC 1000 → 899,含 1.5 手续费)、签名全部卖出收回 96.08(扣 2%);调试推进几天后押中的纯度盘兑付 117 回到钱包(CRC 995 → 1,012),晨间日志"预测押中";此前用 Poinbase 账户的版本还验证过价格盘兑付 199;一个真实的革命军行动被观测到并结算为"是";没押的盘口也进"最近结果"。没有在游戏里验证:常规新闻事件盘口的自然结算(本次测试存档里没碰上)、作废、内幕判定与被查、手册文字、英文界面。派系每天 6% 的先验没有真实数据校准。
-- 单元测试 11628 通过(含多钱包持仓:各回各的钱包、限额与内幕合计)。
+- 单元测试 11647 通过(含多钱包持仓:各回各的钱包、限额与内幕合计;消息反应、情报、gas / CRA / 矿机时代先验)。
+- 游戏内验证(玩法打磨):价格显示、情报页、局势简报(真实读到安保行动已排期等)、三档内幕消息购买与签名、线索栏显示情报与矿机时代预告、买「是」押矿机时代后结算兑付 945。没有在游戏里验证:夜间消息反应的实际价格变化(只有单元测试,夜里跑过没报错)、gas / CRA 盘口的结算(只有单元测试)、报纸排期引起的 92% 靠拢、假情报、被查、英文界面。
### 第六阶段:高收益与高风险(进行中)
diff --git a/src/Decentralized.Domain/Predict.cs b/src/Decentralized.Domain/Predict.cs
index 35116f2..6f2c703 100644
--- a/src/Decentralized.Domain/Predict.cs
+++ b/src/Decentralized.Domain/Predict.cs
@@ -4,7 +4,7 @@ using System.Linq;
namespace Decentralized.Domain
{
- public enum PredictKind { Event, Faction, Purity, Price }
+ public enum PredictKind { Event, Faction, Purity, Price, Gas, Cra, Era }
public enum PredictResult { Ok, Closed, Amount, Funds, Cap, Opposite, NoPosition }
@@ -75,6 +75,14 @@ namespace Decentralized.Domain
public double Shares, Cost, Taint, Basis, Insider;
}
+ /// 情报贩子卖的内幕消息:对某个盘口给出"是 / 否"的说法,三档准确率,可能是假的(与真相相反)。
+ public sealed class PredictTip
+ {
+ public int Market, Tier;
+ public bool Claim;
+ public double Paid;
+ }
+
/// 结算记录(盘口列表下面的"最近结果")。
public sealed class PredictRecord
{
@@ -109,6 +117,7 @@ namespace Decentralized.Domain
public List Markets = new List();
public List Positions = new List();
public List Recent = new List();
+ public List Tips = new List();
public int Seq;
/// 每晚观测:当天新出现的事件标识(和 "F:派系")。窗口里缺任何一天的观测,盘口作废退款。
public Dictionary> Started = new Dictionary>();
@@ -222,6 +231,7 @@ namespace Decentralized.Domain
else Record(day, m, m.Outcome, 0, false);
}
Markets.RemoveAll(m => m.Outcome >= 0);
+ Tips.RemoveAll(t => Find(t.Market) == null);
return evs;
}
@@ -231,6 +241,34 @@ namespace Decentralized.Domain
while (Recent.Count > Tuning.PredictHistory) Recent.RemoveAt(0);
}
+ public PredictTip TipFor(int market) => Tips.Find(t => t.Market == market);
+
+ /// 造一条内幕消息:假情报(概率 IntelFake)说的与真相相反,否则按这一档的准确率说对或说错。
+ public static PredictTip MakeTip(PredictMarket m, bool truth, int tier, Rng rng)
+ {
+ bool claim;
+ if (rng.Chance(Tuning.IntelFake)) claim = !truth;
+ else claim = rng.Chance(Tuning.IntelTipAcc[tier]) ? truth : !truth;
+ return new PredictTip { Market = m.Id, Tier = tier, Claim = claim, Paid = Tuning.IntelTipPrice[tier] * m.MaxLoss };
+ }
+
+ ///
+ /// 夜间的消息反应:还能交易的盘口(明天仍开着)向"公开信息下的合理概率"靠拢 PredictDrift,再加 logit 噪声(NPC 流量)。
+ /// 价格在夜里才反应,所以白天先看到报纸、传闻的人可以抢在前面买、等涨价再卖。target 返回 null 表示这个盘口没有新信息。
+ ///
+ public void Drift(int day, Func target, Rng rng)
+ {
+ foreach (var m in Markets.Where(x => x.Outcome < 0 && x.FromDay > day + 1))
+ {
+ double p = m.PYes, t = target(m) ?? p;
+ t = Math.Max(Tuning.PredictPMin, Math.Min(Tuning.PredictPMax, t));
+ double pt = p + Tuning.PredictDrift * (t - p);
+ double lp = Math.Log(pt / (1 - pt)) + Tuning.PredictNoise * rng.Gauss();
+ pt = Math.Max(Tuning.PredictPMin, Math.Min(Tuning.PredictPMax, 1 / (1 + Math.Exp(-lp))));
+ m.QYes = m.QNo + m.B * Math.Log(pt / (1 - pt));
+ }
+ }
+
/// 读观测:窗口里每一天都有记录才能判断;有任何一天缺就返回 null(作废)。
public bool? Happened(PredictMarket m)
{
@@ -260,7 +298,7 @@ namespace Decentralized.Domain
{
var c = pool[rng.Range(0, pool.Count - 1)];
pool.RemoveAll(x => x.Kind == c.Kind && x.Key == c.Key);
- int from = c.Tomorrow ? day + 1 : day + 1 + rng.Range(0, 1);
+ int from = c.Tomorrow ? day + 1 : day + 1 + rng.Range(0, 2);
int to = c.Tomorrow ? from : from + rng.Range(0, 2);
var m = PredictMarket.Create(++Seq, c.Kind, c.Key, c.Title, c.Param, day, from, to, c.Prob(from, to));
Markets.Add(m); made.Add(m);
@@ -279,6 +317,51 @@ namespace Decentralized.Domain
return 1 - Math.Pow(1 - p1, days);
}
+ /// 某天 gas 不低于阈值的概率(已知相位):周期值 × (1 ± 噪声) 均匀分布。
+ public static double GasAtLeast(int day, int phase, double threshold)
+ {
+ double c = TxRules.GasCycle(day, phase), hi = c * (1 + Tuning.TxGasNoise), lo = c * (1 - Tuning.TxGasNoise);
+ return Math.Max(0, Math.Min(1, (hi - threshold) / (hi - lo)));
+ }
+
+ /// 不知道相位时的先验:七个相位等可能。
+ public static double GasFlat(int day, double threshold)
+ {
+ double s = 0;
+ for (int ph = 0; ph < Tuning.TxGasPeriod; ph++) s += GasAtLeast(day, ph, threshold);
+ return s / Tuning.TxGasPeriod;
+ }
+
+ ///
+ /// CRA 预警在 [from, to] 夜里发出的概率:每晚补贴池先被消耗,归零那晚必发,否则按日概率;
+ /// 窗口之前已经发出的不算(那就是"否")。subsidy / drain 是今天的补贴池和每晚消耗,today 是今晚。
+ ///
+ public static double CraWarnProb(double subsidy, double drain, int today, int from, int to, double dailyHazard)
+ {
+ double noWarn = 1, inWindow = 0;
+ for (int d = today; d <= to; d++)
+ {
+ subsidy -= drain;
+ double h = subsidy <= 0 ? 1 : dailyHazard;
+ if (d >= from) inWindow += noWarn * h;
+ noWarn *= 1 - h;
+ }
+ return inWindow;
+ }
+
+ ///
+ /// 矿机时代在 [from, to] 开始的概率:没出预警时在 [eraMin, eraMax] 里均匀;预警已出(warnDay >= 0)时落在预警后 EraWarnMin..EraWarnMax 天。
+ /// 只算还没过去的日子(today 之后)。
+ ///
+ public static double EraProb(int today, int from, int to, int warnDay)
+ {
+ int lo = warnDay >= 0 ? warnDay + Tuning.EraWarnMin : Tuning.EraMin, hi = warnDay >= 0 ? warnDay + Tuning.EraWarnMax : Tuning.EraMax;
+ lo = Math.Max(lo, today + 1);
+ if (hi < lo) return 0;
+ int a = Math.Max(lo, from), b = Math.Min(hi, to);
+ return b < a ? 0 : (b - a + 1) / (double)(hi - lo + 1);
+ }
+
public static double FactionProb(int days) => 1 - Math.Pow(1 - Tuning.PredictFactionDaily, days);
/// 均匀整数 lo..hi 里不低于 threshold 的概率。
diff --git a/src/Decentralized.Domain/Tuning.cs b/src/Decentralized.Domain/Tuning.cs
index f65a7c4..ad24a5b 100644
--- a/src/Decentralized.Domain/Tuning.cs
+++ b/src/Decentralized.Domain/Tuning.cs
@@ -192,11 +192,17 @@ namespace Decentralized.Domain
/// 同时开放的盘口数量范围、每天最多新开几个;内幕持仓达到上限份额的多少比例后结算被查的概率,查实后收益记罪证的比例与上限;
/// 派系行动的日均开始概率(先验);先验概率的夹紧范围;结算记录保留条数。
public const int PredictUnlockDay = 8;
- public const double PredictFee = 0.02, PredictLoss0 = 300, PredictLossPerDay = 10, PredictCap = 3;
+ public const double PredictFee = 0.02, PredictLoss0 = 800, PredictLossPerDay = 25, PredictCap = 3;
public const int PredictOpenMin = 3, PredictOpenMax = 6, PredictNewPerDay = 2;
public const double PredictInsiderShare = 0.4, PredictInsiderCatch = 0.5, PredictFraudShare = 0.5, PredictFraudCap = 400;
public const double PredictFactionDaily = 0.06, PredictPMin = 0.03, PredictPMax = 0.97;
public const int PredictHistory = 8;
+ /// 盘口对消息的反应:每晚向"公开信息下的合理概率"靠拢的比例,以及盘口 logit 上的随机噪声(NPC 流量)。
+ public const double PredictDrift = 0.5, PredictNoise = 0.35;
+ /// 情报贩子:局势简报价格(CRT)、三档内幕消息的价格(占盘口最大亏损的比例)与准确率、假情报概率(claim 与真相相反)、需要的黑市声望。
+ public const double IntelBriefPrice = 120, IntelFake = 0.10;
+ public static readonly double[] IntelTipPrice = { 0.01, 0.025, 0.05 }, IntelTipAcc = { 0.60, 0.80, 0.95 };
+ public const int IntelRep = 20;
/// 主交易对(信用点一侧)的深度随天数增长。
public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0);
diff --git a/src/Decentralized.Domain/Wallet.cs b/src/Decentralized.Domain/Wallet.cs
index f9bae3e..0306689 100644
--- a/src/Decentralized.Domain/Wallet.cs
+++ b/src/Decentralized.Domain/Wallet.cs
@@ -108,6 +108,14 @@ namespace Decentralized.Domain
}
/// 按价格表估值(信用点)。
+ /// 花掉(销毁)一笔币:污点与成本按这笔币占钱包总价值的比例一起带走。
+ public void Spend(string asset, double amount, Func price)
+ {
+ double total = Value(price), share = total > 0 ? Math.Min(1, amount * price(asset) / total) : 0;
+ double taint = Taint * share, basis = Basis * share;
+ Add(asset, -amount); Taint -= taint; Basis -= basis;
+ }
+
public double Value(Func price) => Balances.Sum(kv => kv.Value * price(kv.Key));
///
diff --git a/src/Decentralized/DealerStock.cs b/src/Decentralized/DealerStock.cs
index 475ace3..204f5fb 100644
--- a/src/Decentralized/DealerStock.cs
+++ b/src/Decentralized/DealerStock.cs
@@ -81,13 +81,21 @@ namespace Decentralized
{
var d = Session.Data; int day = StoreStation.GetDayCounter();
d.Predict.Markets.Clear();
- var made = d.Predict.Refill(day, d.Rng, PredictGame.Candidates(day));
+ var made = new List(); for (int i = 0; i < 4; i++) made.AddRange(d.Predict.Refill(day, d.Rng, PredictGame.Candidates(day)));
Log.Info($"测试:新开 {made.Count} 个预测盘口(不看解锁日)");
}));
+ ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Predict: era + CRA markets (test)", () =>
+ {
+ var d = Session.Data; int day = StoreStation.GetDayCounter(); var bk = d.Predict;
+ bk.Markets.Add(PredictMarket.Create(++bk.Seq, PredictKind.Era, "era", L.T("predict.era"), 0, day, day + 1, day + 3, PredictMath.EraProb(day, day + 1, day + 3, -1) + 0.05));
+ double h = 1 - System.Math.Pow(1 - Tuning.CraSpiralWeekly, 1.0 / 7);
+ bk.Markets.Add(PredictMarket.Create(++bk.Seq, PredictKind.Cra, "cra", "CRA", 0, day, day + 1, day + 3, PredictMath.CraWarnProb(d.Stable.CraSubsidy, (Tuning.CraNpcStake + d.Staking.Earning) * d.Stable.CraRate, day, day + 1, day + 3, h)));
+ Log.Info($"测试:矿机时代 {d.Network.EraDay},CRA 补贴池 {d.Stable.CraSubsidy:0}");
+ });
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Predict: window = tomorrow (test)", () => { foreach (var m in Session.Data.Predict.Markets) { m.FromDay = m.ToDay = StoreStation.GetDayCounter() + 1; } Log.Info("测试:所有盘口窗口改成明天"); });
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Perp fund 0 (test)", () => { Session.Data.Perps.InsuranceFund = 0; Log.Info("测试:保险基金清零"); });
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "SPARK x0.3 (test)", () => { var st = Session.Data.Stable; st.Spark *= 0.3; Log.Info($"测试:SPARK 价格 {st.Spark:0.00}"); });
- ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Wallet gift: CRC, CRA, PSC (test)", () => { var w = Session.Data.Hot; w.Add(Assets.CRA, 1000); w.Add(Assets.CRC, 1000); w.Add(Assets.PSC, 20); w.Basis += 2000; Log.Info($"测试:{WalletWindow.NameOf(w)} CRA {w.Get(Assets.CRA):0}"); });
+ ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Wallet gift: CRC, CRA, PSC (test)", () => { var w = Session.Data.Hot; w.Add(Assets.CRA, 1000); w.Add(Assets.CRC, 1000); w.Add(Assets.CRT, 3000); w.Add(Assets.PSC, 20); w.Basis += 5000; Log.Info($"测试:{WalletWindow.NameOf(w)} CRA {w.Get(Assets.CRA):0}"); });
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "CRA spiral warning (test)", () =>
{
var st = Session.Data.Stable; int day = StoreStation.GetDayCounter();
diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv
index 9b25b98..ab720a1 100644
--- a/src/Decentralized/ModFiles/Localization/en.csv
+++ b/src/Decentralized/ModFiles/Localization/en.csv
@@ -268,7 +268,7 @@ black.kind.Contraband.3,Contraband (high)
black.kind.Tech,High-tech (markup)
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
item.web3guide.name,Web3 Handbook
-item.web3guide.desc,"Bank: depositing turns credits into CRC in your Poinbase account; withdrawing turns CRC back into credits and goes through compliance screening, which looks at the Poinbase account's taint. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Hardware wallets: on-chain coins sit in PLEDGER hardware wallets, each with its own balances and taint; open and connect them on the terminal's Wallets page. The Web3 page's trading, black market and mixer use the connected wallet, and coin-paying customers and mining rewards go into it too. Payments (black market, mixer, Ponero desk, listing fees) start as an invoice on the terminal; type the payee address on the wallet and hold ✓ to sign. The last two characters of an address are a checksum, so a one-character typo is usually caught; once in a while it happens to be valid and the coins go to an address nobody uses. DEX swaps also need a signature on the wallet. RatDigg miners (M1 / M2 / M3) are standalone machines with far more hash than a card and a lower power draw per hash; they need a battery, take modules and use no rig slots. Only the black market's miner dealers sell them, gated by black-market standing (M1 40, M2 60, M3 80), sometimes second-hand. When one burns out, repair it by putting electronic components in its repair slot in order; it comes back 20 health lower. Somewhere between day 50 and day 90 a "miner era" arrives, announced in the paper a few days earlier: the big farms halve their power costs, network hash climbs, GPU miners are diluted and low-end cards barely earn, while miners still do fine. Perps: the Perps tab on Poinbase uses your account CRC as margin, up to 5× on PSC and 3× on SPARK, one position per product, never long and short at once. Fills are worse than the quote (more so for bigger orders, like the spot pool) and opening and closing both cost a fee. Liquidation is checked only overnight against the day's average price: equity under 10% of notional wipes the margin. There is also a nightly funding payment; when the price is above its 7-day average, longs pay shorts. If a liquidation loses more than the margin, the insurance fund covers the gap; when it runs dry (a SPARK collapse can do that) winning positions are auto-deleveraged and give up half their profit; anything beyond that becomes your debt. Debt grows 2% a day, is repaid first from CRC in the account every night, blocks new positions and cash-outs, and after seven days items in your shop are seized to cover it; whatever is still short is written off and the perp account is banned for 30 days. Poinbase is a custodial account and can withdraw straight to the connected wallet; depositing from a wallet means sending to Poinbase's deposit address, which takes only CRC and PSC. More hardware wallets are sold by Dr. Jackson; drag one onto the trading terminal to add it, up to 5. The black market has cheaper ones. A wallet you no longer want can be destroyed on the Wallets page once its coins are moved out. Transfers pay a fee: slow, normal or fast, and gas prices rise and fall over roughly a week; when the network is congested fees rise and slow transactions may be dropped from the mempool. Check the full address before sending. Staking: CRA staked in the Revolution pool earns a daily rate that compounds into the stake; withdrawing means requesting first and waiting four days, with no interest while waiting and no way to take it early. The pool pays from a subsidy fund; when it runs low the rate is cut and word of big holders pulling out may spread. Treat that as a warning: once it collapses CRA is worthless within three days and four days of unlocking is too slow. The balance token SPARK can be minted and burned against CRA at the going price (with a fee); its price swings hard and crashes with the collapse. Staking, minting and burning are contract calls and need a signature on the wallet. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell tab, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout, which also grows with how much you sold in the last 7 days; at most 10 listings at a time, 5% listing fee, repricing a listing costs 2% and the sale speed follows the new multiplier, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Anonymous-coin swap (Web3 trade page, needs black-market standing): Ponero is a privacy coin and swapping in wipes the taint on that value; 6% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter. Prediction markets (Web3 Predict page, from game day 8): each market is a yes/no question about things the game itself rolls or schedules: a news event appearing, a faction operation starting, tomorrow's water purity, the PSC price. A market is open until its window starts and settles on the night of the window's last day. Buy Yes or No with CRC from your on-chain wallet (a contract call: sign on the wallet; the position belongs to the signing wallet); the price is the market's implied probability and rises as you buy; each winning share pays 1 credit; 2% fee on each trade; each market has a position limit. Winnings inherit the taint ratio of the stake. Rumor slips and faction perks can tell you ahead of time that something will happen; betting Yes on such a market is an insider bet. A large insider position has a 50% chance of being caught at settlement: only your stake is returned and half the profit goes on record as fraud. If game data is missing for the window (for example you skipped the nightly settlement), the market is void and the stake is refunded."
+item.web3guide.desc,"Bank: depositing turns credits into CRC in your Poinbase account; withdrawing turns CRC back into credits and goes through compliance screening, which looks at the Poinbase account's taint. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Hardware wallets: on-chain coins sit in PLEDGER hardware wallets, each with its own balances and taint; open and connect them on the terminal's Wallets page. The Web3 page's trading, black market and mixer use the connected wallet, and coin-paying customers and mining rewards go into it too. Payments (black market, mixer, Ponero desk, listing fees) start as an invoice on the terminal; type the payee address on the wallet and hold ✓ to sign. The last two characters of an address are a checksum, so a one-character typo is usually caught; once in a while it happens to be valid and the coins go to an address nobody uses. DEX swaps also need a signature on the wallet. RatDigg miners (M1 / M2 / M3) are standalone machines with far more hash than a card and a lower power draw per hash; they need a battery, take modules and use no rig slots. Only the black market's miner dealers sell them, gated by black-market standing (M1 40, M2 60, M3 80), sometimes second-hand. When one burns out, repair it by putting electronic components in its repair slot in order; it comes back 20 health lower. Somewhere between day 50 and day 90 a "miner era" arrives, announced in the paper a few days earlier: the big farms halve their power costs, network hash climbs, GPU miners are diluted and low-end cards barely earn, while miners still do fine. Perps: the Perps tab on Poinbase uses your account CRC as margin, up to 5× on PSC and 3× on SPARK, one position per product, never long and short at once. Fills are worse than the quote (more so for bigger orders, like the spot pool) and opening and closing both cost a fee. Liquidation is checked only overnight against the day's average price: equity under 10% of notional wipes the margin. There is also a nightly funding payment; when the price is above its 7-day average, longs pay shorts. If a liquidation loses more than the margin, the insurance fund covers the gap; when it runs dry (a SPARK collapse can do that) winning positions are auto-deleveraged and give up half their profit; anything beyond that becomes your debt. Debt grows 2% a day, is repaid first from CRC in the account every night, blocks new positions and cash-outs, and after seven days items in your shop are seized to cover it; whatever is still short is written off and the perp account is banned for 30 days. Poinbase is a custodial account and can withdraw straight to the connected wallet; depositing from a wallet means sending to Poinbase's deposit address, which takes only CRC and PSC. More hardware wallets are sold by Dr. Jackson; drag one onto the trading terminal to add it, up to 5. The black market has cheaper ones. A wallet you no longer want can be destroyed on the Wallets page once its coins are moved out. Transfers pay a fee: slow, normal or fast, and gas prices rise and fall over roughly a week; when the network is congested fees rise and slow transactions may be dropped from the mempool. Check the full address before sending. Staking: CRA staked in the Revolution pool earns a daily rate that compounds into the stake; withdrawing means requesting first and waiting four days, with no interest while waiting and no way to take it early. The pool pays from a subsidy fund; when it runs low the rate is cut and word of big holders pulling out may spread. Treat that as a warning: once it collapses CRA is worthless within three days and four days of unlocking is too slow. The balance token SPARK can be minted and burned against CRA at the going price (with a fee); its price swings hard and crashes with the collapse. Staking, minting and burning are contract calls and need a signature on the wallet. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell tab, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout, which also grows with how much you sold in the last 7 days; at most 10 listings at a time, 5% listing fee, repricing a listing costs 2% and the sale speed follows the new multiplier, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Anonymous-coin swap (Web3 trade page, needs black-market standing): Ponero is a privacy coin and swapping in wipes the taint on that value; 6% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter. Prediction markets (Web3 Predict page, from game day 8): each market is a yes/no question about things the game itself rolls or schedules: a news event appearing, a faction operation starting, tomorrow's water purity, the PSC price. A market is open until its window starts and settles on the night of the window's last day. Buy Yes or No with CRC from your on-chain wallet (a contract call: sign on the wallet; the position belongs to the signing wallet); the price is the market's implied probability and rises as you buy; each winning share pays 1 credit; 2% fee on each trade; each market has a position limit. Winnings inherit the taint ratio of the stake. Rumor slips and faction perks can tell you ahead of time that something will happen; betting Yes on such a market is an insider bet. A large insider position has a 50% chance of being caught at settlement: only your stake is returned and half the profit goes on record as fraud. If game data is missing for the window (for example you skipped the nightly settlement), the market is void and the stake is refunded. Prices react to news: each night every open market moves halfway toward the fair probability given public information, plus some noise; so whoever reads the paper, a rumor slip or a faction perk first can buy ahead and sell after the move instead of waiting for settlement. The clue list in a market panel shows what you know about it: schedules, cooldowns, the halving day, recent gas values, the CRA subsidy pool level, ASIC-era announcements. Patterns you can work out: faction operations are triggered by hidden power values at thresholds (for example a crackdown needs security >= 20 and black market >= 40, and drops security by 20); gas runs on a seven-day cycle; the price anchor ramps up the week before a halving; scheduled crackdowns and blackouts move the anchor but the market price does not reflect them ahead of time; the faster the subsidy drains (the more is staked) the earlier CRA warns; the PSC price market can be moved by your own big orders. The intel broker (needs enough black-market standing) sells a situation brief (the five power values and what each faction can launch now, in CRT) and three grades of insider tip (only for faction operations and the ASIC era, which are already decided; about 60 / 80 / 95% accurate; priced by market size; one in ten is fake and says the opposite of the truth); betting the tipped side counts as an insider bet. Market size grows with the day and a wrong bet loses the whole stake, so do not put everything on one market."
courier.odd1,...Package is here.
courier.odd2,"(His hand stays in his pocket.) Sign here."
courier.sign,Sign
@@ -540,20 +540,20 @@ predict.h,Prediction markets
predict.acct,On-chain wallet {0}: CRC {1}
predict.none,No open markets today.
predict.closed,closed
-predict.row,Yes {0}% No {1}%
-predict.held,"holding {0}: cost {1}, pays {2}"
+predict.row,Yes {0} No {1}
+predict.held,"holding {0} x{1}, cost {2}"
predict.yes,Yes
predict.no,No
predict.void,void
predict.result,Day {0} {1} result: {2} {3}
predict.pick,Trade
-predict.price,Yes {0}% No {1}%
-predict.pos,"{0}: {1}, pays {2}"
-predict.pos2,"Cost {0} sells for {1} now"
+predict.price,Price per share: Yes {0} No {1}
+predict.pos,"{0}: {1} x{2}"
+predict.pos2,"Cost {0} pays {1}"
predict.sum.buy,"Buy {1} for {2} CRC: {0}"
predict.sum.sell,Sell prediction position: {0}
predict.closed.note,"Closed, waiting for settlement."
-predict.preview,"Buy {0}: pays {1}, net {2} if right"
+predict.preview,"Buy {0}: about {1} shares (avg {2}), net {3} if right"
predict.buy.yes,Buy Yes
predict.buy.no,Buy No
predict.sell,Sell all
@@ -582,3 +582,45 @@ nightlog.predict.win,"Prediction right: ""{0}"", paid out {1}."
nightlog.predict.lose,"Prediction wrong: ""{0}"", stake {1} lost."
nightlog.predict.void,"Prediction void: ""{0}"", stake {1} refunded."
nightlog.predict.caught,"Insider bet caught: ""{0}"", profit {1} confiscated, fraud on record."
+predict.tab.markets,Markets
+predict.tab.intel,Intel
+predict.acct.crt,On-chain wallet {0}: CRT {1}
+predict.tier.0,low
+predict.tier.1,mid
+predict.tier.2,high
+predict.clue.tip,Tip ({0}): says {1}
+predict.clue.queued,Scheduled: starts day {0}
+predict.clue.queued2,"Scheduled: day {0} ""{1}"""
+predict.clue.cool,Event active or on cooldown
+predict.clue.busy,Faction op running; no new one soon
+predict.clue.halving,Halving day {0}; anchor ramps the week before
+predict.clue.anchor,Price anchor now {0}
+predict.clue.gas,Recent gas: {0}
+predict.clue.subsidy,Subsidy pool {0}%; empty forces a warning
+predict.clue.drain,Drain per night {0}
+predict.clue.era,"Random in days {0}-{1}, announced ahead"
+predict.clue.era.warned,Announced: starts days {0}-{1}
+predict.q.gas,{0}: gas index >= {1}
+predict.q.cra,{0}: CRA warning issued
+predict.q.era,{0}: ASIC era starts
+predict.era,ASIC era
+predict.pos3,Avg {0} now {1}
+predict.pos4,Selling now returns {0}
+intel.h,Intel broker
+intel.locked,Needs the Web3 black market and standing {0}.
+intel.brief.buy,Buy situation brief ({0} CRT)
+intel.brief.none,No brief bought yet.
+intel.brief.day,Brief from day {0}
+intel.tips.h,Insider tips
+intel.tips.none,Nothing to ask about (only faction ops and the ASIC era).
+intel.tip.buy,About {0}% accurate {1} CRT
+intel.err.funds,"Not enough CRT in the wallet, or the market can no longer be bought."
+intel.brief.sum,"Situation brief, {0} CRT"
+intel.tip.sum,Insider tip {0} CRT: {1}
+intel.brief.done,Got the brief.
+intel.tip.done,They say: {0}.
+predict.brief.levels,Security {0} Rev {1} Black mkt {2} Unrest {3} Upper {4}
+predict.brief.busy,{0}: operation running
+predict.brief.queued,{0}: operation scheduled
+predict.brief.none,{0}: nothing to launch
+predict.brief.can,{0}: can launch {1}
diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv
index 71deb99..c9455be 100644
--- a/src/Decentralized/ModFiles/Localization/zh.csv
+++ b/src/Decentralized/ModFiles/Localization/zh.csv
@@ -267,7 +267,7 @@ black.kind.Contraband.3,违禁品(高)
black.kind.Tech,高科技物品(加价)
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
item.web3guide.name,Web3 手册
-item.web3guide.desc,"银行:存入把信用点换成 Poinbase 账户里的 CRC;提取把 CRC 换回信用点,要过合规审查,审查看 Poinbase 账户的污点。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。硬件钱包:链上的币放在 PLEDGER 硬件钱包里,每个钱包的余额和污点各算各的;在终端的钱包页打开、连接。Web3 页的交易、黑市、混币器用已连接的钱包,用币付款的顾客和挖矿收益也进它。付款(黑市、混币器、Ponero 兑换处、上架费)先在终端开账单,再在钱包里手输收款地址、按住 ✓ 签名;地址末尾两位是校验位,输错一位多半会被拦下,偶尔碰巧合法,币就转进了没人用的地址。DEX 兑换也要在钱包上签名。Poinbase 是托管账户,可以直接提到已连接的钱包;从钱包充值要转到 Poinbase 的充值地址,只收 CRC 和 PSC。更多硬件钱包向杰克逊博士购买,拖到交易终端上添加,最多 5 个;黑市上也有更便宜的。不要的钱包可以在钱包页销毁,要先把币转走。转账要付手续费,分慢、普通、快三档,gas 价格大约一周一个起落;网络拥堵时手续费变贵,慢档可能被挤出内存池。转账前核对完整地址。质押:把 CRA 质押进革命军质押池每天有日息,利息并入本金;取出要先申请,等四天才到账,等待期间没有利息,也不能提前拿。质押池靠补贴池付息,补贴池见底会降息,有时还会传出大户撤出的消息,那是预兆:一旦崩盘,三天内 CRA 就一文不值,四天的解锁期来不及。平衡代币 SPARK 可以和 CRA 按价格互相铸销(收手续费),价格波动很大,崩盘时会跟着暴跌。质押、铸销都是合约调用,要在钱包上签名。RatDigg 矿机(M1 / M2 / M3)是独立的机器,算力比显卡大得多、耗电更省,要放电池才挖,可以放模块,不占矿架槽位;只在黑市的矿机商那里有,要黑市声望(M1 40、M2 60、M3 80),偶尔有二手的。暴毙后要往修理槽里放电子元件按顺序修好,修好健康少 20。第 50 到 90 天之间会有一次"矿机时代",提前几天上报纸预告,之后大矿场的耗电成本砍半,全网算力涨上去,显卡矿工的收益被摊薄,低档显卡几乎不赚钱,矿机还行。永续合约:Poinbase 的"永续"页,用账户里的 CRC 当保证金,PSC 最高 5 倍、SPARK 最高 3 倍,一个品种只有一个仓位,不能同时多空。成交价比现价差一截(单越大差得越多,和现货池一样),开平仓还收手续费。强平只在夜里按当天均价检查,权益不到名义价值的 10% 就被强平,保证金没了;每晚还有资金费,价格比近 7 天均价高时多头付给空头。强平后亏光保证金还不够的缺口,先由保险基金垫,基金见底(SPARK 崩盘这类大行情会抽干它)后盈利的仓位会被自动减仓、少拿一半盈利,再不够就记成你的欠款:欠款每天涨 2%,账户里的 CRC 每晚先拿去还,没还清不能开仓、不能出金,七天不还就没收店里的物品抵债,抵不够的核销并拉黑永续账户 30 天。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的卖货页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;同时最多上架 10 件,上架收 5% 上架费,上架后可以改价(收 2% 改价费,标价按参考价重算,成交的快慢跟着新倍率走),成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。匿名币兑换(Web3 交易页,要黑市声望):Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 6% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。 预测市场(Web3 预测页,游戏第 8 天起):每个盘口是一道是非题,只问游戏自己掷骰或排期的事:新闻事件出现、派系行动开始、明天的水纯度、PSC 价格。盘口开放到窗口开始前,窗口最后一天夜里结算;买「是」「否」用链上钱包的 CRC(合约调用,要在钱包上签名,持仓记在签名的钱包名下),价格就是市场认为的概率,买得越多越贵,押中每份兑付 1 信用点,买卖各收 2%。每个盘口有持仓上限。押中的钱继承押注资金的污点比例。传闻纸条、派系特权让你提前知道某件事会发生,在这类盘口押「是」算内幕单;内幕持仓太大,结算时有一半概率被查,只退本金,收益的一半记诈骗罪证。窗口里缺了游戏数据(例如那几天没睡觉结算),盘口作废退回本金。"
+item.web3guide.desc,"银行:存入把信用点换成 Poinbase 账户里的 CRC;提取把 CRC 换回信用点,要过合规审查,审查看 Poinbase 账户的污点。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。硬件钱包:链上的币放在 PLEDGER 硬件钱包里,每个钱包的余额和污点各算各的;在终端的钱包页打开、连接。Web3 页的交易、黑市、混币器用已连接的钱包,用币付款的顾客和挖矿收益也进它。付款(黑市、混币器、Ponero 兑换处、上架费)先在终端开账单,再在钱包里手输收款地址、按住 ✓ 签名;地址末尾两位是校验位,输错一位多半会被拦下,偶尔碰巧合法,币就转进了没人用的地址。DEX 兑换也要在钱包上签名。Poinbase 是托管账户,可以直接提到已连接的钱包;从钱包充值要转到 Poinbase 的充值地址,只收 CRC 和 PSC。更多硬件钱包向杰克逊博士购买,拖到交易终端上添加,最多 5 个;黑市上也有更便宜的。不要的钱包可以在钱包页销毁,要先把币转走。转账要付手续费,分慢、普通、快三档,gas 价格大约一周一个起落;网络拥堵时手续费变贵,慢档可能被挤出内存池。转账前核对完整地址。质押:把 CRA 质押进革命军质押池每天有日息,利息并入本金;取出要先申请,等四天才到账,等待期间没有利息,也不能提前拿。质押池靠补贴池付息,补贴池见底会降息,有时还会传出大户撤出的消息,那是预兆:一旦崩盘,三天内 CRA 就一文不值,四天的解锁期来不及。平衡代币 SPARK 可以和 CRA 按价格互相铸销(收手续费),价格波动很大,崩盘时会跟着暴跌。质押、铸销都是合约调用,要在钱包上签名。RatDigg 矿机(M1 / M2 / M3)是独立的机器,算力比显卡大得多、耗电更省,要放电池才挖,可以放模块,不占矿架槽位;只在黑市的矿机商那里有,要黑市声望(M1 40、M2 60、M3 80),偶尔有二手的。暴毙后要往修理槽里放电子元件按顺序修好,修好健康少 20。第 50 到 90 天之间会有一次"矿机时代",提前几天上报纸预告,之后大矿场的耗电成本砍半,全网算力涨上去,显卡矿工的收益被摊薄,低档显卡几乎不赚钱,矿机还行。永续合约:Poinbase 的"永续"页,用账户里的 CRC 当保证金,PSC 最高 5 倍、SPARK 最高 3 倍,一个品种只有一个仓位,不能同时多空。成交价比现价差一截(单越大差得越多,和现货池一样),开平仓还收手续费。强平只在夜里按当天均价检查,权益不到名义价值的 10% 就被强平,保证金没了;每晚还有资金费,价格比近 7 天均价高时多头付给空头。强平后亏光保证金还不够的缺口,先由保险基金垫,基金见底(SPARK 崩盘这类大行情会抽干它)后盈利的仓位会被自动减仓、少拿一半盈利,再不够就记成你的欠款:欠款每天涨 2%,账户里的 CRC 每晚先拿去还,没还清不能开仓、不能出金,七天不还就没收店里的物品抵债,抵不够的核销并拉黑永续账户 30 天。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的卖货页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;同时最多上架 10 件,上架收 5% 上架费,上架后可以改价(收 2% 改价费,标价按参考价重算,成交的快慢跟着新倍率走),成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。匿名币兑换(Web3 交易页,要黑市声望):Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 6% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。 预测市场(Web3 预测页,游戏第 8 天起):每个盘口是一道是非题,只问游戏自己掷骰或排期的事:新闻事件出现、派系行动开始、明天的水纯度、PSC 价格。盘口开放到窗口开始前,窗口最后一天夜里结算;买「是」「否」用链上钱包的 CRC(合约调用,要在钱包上签名,持仓记在签名的钱包名下),价格就是市场认为的概率,买得越多越贵,押中每份兑付 1 信用点,买卖各收 2%。每个盘口有持仓上限。押中的钱继承押注资金的污点比例。传闻纸条、派系特权让你提前知道某件事会发生,在这类盘口押「是」算内幕单;内幕持仓太大,结算时有一半概率被查,只退本金,收益的一半记诈骗罪证。窗口里缺了游戏数据(例如那几天没睡觉结算),盘口作废退回本金。 价格会对消息作出反应:每晚盘口向当时公开信息下的合理概率靠拢一半,再加一点随机波动;所以先从报纸、传闻纸条、派系特权看到消息的人可以抢在前面买,涨价后卖出,不用等到结算。盘口面板的线索栏列出和这个盘口有关的已知信息:排期、冷却、减半日、gas 近几天的数值、CRA 补贴池水位、矿机时代预告。可以推演的规律:派系行动由隐藏的势力值按阈值触发(例如镇压要安保 ≥ 20 且黑市 ≥ 40,镇压后安保 −20);gas 七天一个周期;减半前一周价格锚逐日抬高;排期好的镇压和停电会压低或抬高锚,但盘口价格不会预先反映;补贴池消耗越快(质押越多)CRA 越早预警;PSC 价格盘的价格能被你自己的大单推动。情报贩子(黑市声望够了才有)卖局势简报(五个势力值和各派系现在能发起的行动,CRT)和三档内幕消息(只针对已经定下来的派系行动、矿机时代盘口,准确率约 60 / 80 / 95%,价格按盘口规模,一成是假情报,说的与真相相反);买了内幕消息再押同一边算内幕单。盘口规模随天数增长,押错本金全亏,别把钱都压在一个盘口上。"
courier.odd1,……东西到了。
courier.odd2,"(他的手一直放在口袋里)在这儿签个字。"
courier.sign,签收
@@ -539,20 +539,20 @@ predict.h,预测市场
predict.acct,链上钱包 {0}:CRC {1}
predict.none,今天没有开放的盘口。
predict.closed,已封盘
-predict.row,是 {0}% 否 {1}%
-predict.held,持有「{0}」成本 {1},押中得 {2}
+predict.row,是 {0} 否 {1}
+predict.held,持有「{0}」{1} 份,成本 {2}
predict.yes,是
predict.no,否
predict.void,作废
predict.result,第 {0} 天 {1} 结果:{2} {3}
predict.pick,买卖
-predict.price,是 {0}% 否 {1}%
-predict.pos,{0}:押「{1}」,押中得 {2}
-predict.pos2,成本 {0} 现在卖出得 {1}
+predict.price,每份现价:是 {0} 否 {1}
+predict.pos,{0}:押「{1}」{2} 份
+predict.pos2,成本 {0} 押中得 {1}
predict.sum.buy,"买入「{1}」,花 {2} CRC:{0}"
predict.sum.sell,卖出预测持仓:{0}
predict.closed.note,已封盘,等结算。
-predict.preview,买「{0}」:押中得 {1},净赚 {2}
+predict.preview,买「{0}」约 {1} 份(均价 {2}),押中净赚 {3}
predict.buy.yes,买入「是」
predict.buy.no,买入「否」
predict.sell,全部卖出
@@ -581,3 +581,45 @@ nightlog.predict.win,预测押中:「{0}」,兑付 {1}。
nightlog.predict.lose,预测押错:「{0}」,本金 {1} 没了。
nightlog.predict.void,预测作废:「{0}」,退回本金 {1}。
nightlog.predict.caught,内幕下注被查:「{0}」,收益 {1} 没收,记诈骗罪证。
+predict.tab.markets,盘口
+predict.tab.intel,情报
+predict.acct.crt,链上钱包 {0}:CRT {1}
+predict.tier.0,低档
+predict.tier.1,中档
+predict.tier.2,高档
+predict.clue.tip,情报({0}):说「{1}」
+predict.clue.queued,排期:第 {0} 天开始
+predict.clue.queued2,排期:第 {0} 天「{1}」
+predict.clue.cool,该事件进行中或冷却中
+predict.clue.busy,该派系行动进行中,近期无新行动
+predict.clue.halving,减半在第 {0} 天,前一周抬价
+predict.clue.anchor,当前价格锚 {0}
+predict.clue.gas,近几天 gas:{0}
+predict.clue.subsidy,补贴池 {0}%,归零必预警
+predict.clue.drain,每晚消耗 {0}
+predict.clue.era,第 {0}–{1} 天间随机,提前预告
+predict.clue.era.warned,预告已出:第 {0}–{1} 天开始
+predict.q.gas,{0}:gas 指数 ≥ {1}
+predict.q.cra,{0}:CRA 发出预警
+predict.q.era,{0}:矿机时代开始
+predict.era,矿机时代
+predict.pos3,均价 {0} 现价 {1}
+predict.pos4,现在卖出得 {0}
+intel.h,情报贩子
+intel.locked,需要 Web3 黑市和声望 {0}。
+intel.brief.buy,买局势简报({0} CRT)
+intel.brief.none,还没买过简报。
+intel.brief.day,第 {0} 天的简报
+intel.tips.h,内幕消息
+intel.tips.none,没有可以打听的盘口(只有派系行动和矿机时代)。
+intel.tip.buy,准确率约 {0}% {1} CRT
+intel.err.funds,钱包里的 CRT 不够,或这个盘口已经不能买。
+intel.brief.sum,局势简报,{0} CRT
+intel.tip.sum,内幕消息 {0} CRT:{1}
+intel.brief.done,拿到简报了。
+intel.tip.done,对方说:「{0}」。
+predict.brief.levels,安保 {0} 革命军 {1} 黑市 {2} 动荡 {3} 上层 {4}
+predict.brief.busy,{0}:行动进行中
+predict.brief.queued,{0}:行动已排期
+predict.brief.none,{0}:暂时发起不了
+predict.brief.can,{0}:可发起 {1}
diff --git a/src/Decentralized/Night.cs b/src/Decentralized/Night.cs
index be99360..ae66811 100644
--- a/src/Decentralized/Night.cs
+++ b/src/Decentralized/Night.cs
@@ -84,6 +84,7 @@ namespace Decentralized
{
if (d.GasPhase < 0) d.GasPhase = d.Rng.Range(0, Tuning.TxGasPeriod - 1);
d.Gas = TxRules.Gas(day + 1, d.GasPhase, d.Rng);
+ d.GasHist.Add(d.Gas); while (d.GasHist.Count > 10) d.GasHist.RemoveAt(0);
Log.Info($"明天 gas ×{d.Gas:0.00}");
if (day >= d.CongestedUntil && d.Rng.Chance(Tuning.TxCongestionChance))
{
@@ -183,8 +184,7 @@ namespace Decentralized
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);
// 预测市场:先登记今晚的观测再结算到期的盘口;行情盘读夜间更新之前的价格
- var predictEvents = PredictGame.Available(day) || d.Predict.Markets.Count > 0
- ? d.Predict.Night(day, PredictGame.ActiveIds(), m => PredictGame.Outcome(m, d), d.WalletAt, d.Rng) : new List();
+ PredictGame.ClosePrice = d.Market.Price; PredictGame.CloseGas = d.Gas;
// 永续:用今天的标记价(当日均价)结算,要在今晚的价格更新之前
var perpEvents = d.Perps.Night(day, new Dictionary { ["PSC"] = d.Market.Mark, ["SPARK"] = d.Stable.Spark }, d.Poinbase, Session.Price);
if (perpEvents.Any(e => e.Kind == "seize")) SeizeForDebt(d, day);
@@ -229,6 +229,9 @@ namespace Decentralized
case Network.EraStep.Warn: News.Show("pd_news_erawarn_" + day, L.T("news.erawarn.title"), L.T("news.erawarn.desc"), 5); break;
case Network.EraStep.Start: News.Show("pd_news_era_" + day, L.T("news.era.title"), L.T("news.era.desc"), 5); Log.Info("矿机时代到来:NPC 耗电效率翻倍"); break;
}
+ var predictEvents = PredictGame.Available(day) || d.Predict.Markets.Count > 0
+ ? d.Predict.Night(day, PredictGame.ActiveIds(), m => PredictGame.Outcome(m, d), d.WalletAt, d.Rng) : new List();
+ d.Predict.Drift(day, m => PredictGame.PublicProb(m, d, day), d.Rng);
foreach (var e in predictEvents)
{
if (e.Kind == "win") ps.AddNightLog(L.T("nightlog.predict.win", PredictGame.Question(e.Market), Fmt.Credits(e.Amount)));
diff --git a/src/Decentralized/PredictGame.cs b/src/Decentralized/PredictGame.cs
index 90ebf66..35821f4 100644
--- a/src/Decentralized/PredictGame.cs
+++ b/src/Decentralized/PredictGame.cs
@@ -6,16 +6,24 @@ using Decentralized.Domain;
namespace Decentralized
{
///
- /// 预测市场的接入层:按游戏当前状态给出候选盘口,夜间读游戏事件、派系行动、水纯度和本 mod 价格来结算,判断下注时玩家是否已经知道答案。
+ /// 预测市场的接入层:按游戏当前状态给出候选盘口、公开信息下的合理概率(盘口夜里向它靠拢)、线索、情报贩子的情报,
+ /// 夜间读游戏事件、派系行动、水纯度、gas 和本 mod 的状态来结算。
///
public static class PredictGame
{
public static readonly string[] Factions = { "FACTION_SECURITY", "FACTION_REVOLUTION", "FACTION_LOWER_LEVEL", "FACTION_UPPER_LEVEL", "FACTION_BLACK_MARKET" };
private static readonly double[] GutterCuts = { 8200, 8400, 8600, 8800 }, RustCuts = { 9030, 9060, 9090, 9120 };
private static readonly double[] PriceMult = { 0.9, 0.95, 1.0, 1.05, 1.1 };
+ private static readonly double[] GasCuts = { 0.8, 1.0, 1.2, 1.4 };
+
+ /// 夜间结算开始时记下的收盘数据:行情盘读夜间更新前的价格,gas 盘读当天的 gas(接着链上夜间事件就会改成明天的)。
+ public static double ClosePrice, CloseGas;
public static bool Available(int day) => Session.Data != null && Unlock.IsUnlocked && day >= Tuning.PredictUnlockDay;
+ /// 情报贩子的门路:Web3 黑市解锁,且声望够。
+ public static bool BrokerOpen => Unlock.BlackUnlocked && StoreReputation.GetBMReputation() >= Tuning.IntelRep;
+
/// 夜间观测用的标识:生效中的事件,以及派系行动的 "F:派系"。
public static List ActiveIds()
{
@@ -30,15 +38,50 @@ namespace Decentralized
return list;
}
+ // ——— 游戏事件模型 ———
+
+ private static double DayChance(StoreEventManager mgr)
+ {
+ int c = mgr.GetEventCounter();
+ return c == 0 ? 0.50 : c == 1 ? 0.35 : c == 2 ? 0.25 : 0;
+ }
+
+ private static double EventProb(StoreEventManager mgr, string id, int days)
+ {
+ var pool = StoreEventManager.normalEventBlueprints.Where(e => !mgr.IsEventOnCooldown(e.identifier)).ToList();
+ var bp = pool.Find(e => e.identifier == id);
+ if (bp == null) return 0;
+ return PredictMath.EventProb(DayChance(mgr), bp.odd, pool.Sum(e => (double)e.odd), days);
+ }
+
+ /// 已经排进日程的事件 / 派系行动在窗口里的开始日(known = 只看玩家已知的)。
+ private static int? QueuedStart(PredictMarket m, bool knownOnly)
+ {
+ var mgr = StoreStation.Instance?.storeEventManager;
+ if (mgr == null || (m.Kind != PredictKind.Event && m.Kind != PredictKind.Faction)) return null;
+ int today = StoreStation.GetDayCounter();
+ for (int i = 0; i < mgr.futurEvents.Count; i++)
+ {
+ var e = mgr.futurEvents[i];
+ if (knownOnly && !e.IsKnown) continue;
+ bool match = m.Kind == PredictKind.Event ? e.identifier == m.Key : e.isFactionOperation && e.sourceFaction == m.Key;
+ int start = today + mgr.futurEventsDay[i];
+ if (match && start >= m.FromDay && start <= m.ToDay) return start;
+ }
+ return null;
+ }
+
+ private static bool FactionBusy(StoreEventManager mgr, string faction) => mgr.GetActiveEvents().Any(e => e.isFactionOperation && e.sourceFaction == faction);
+
+ // ——— 候选盘口 ———
+
public static List Candidates(int day)
{
var d = Session.Data;
var list = new List();
var mgr = StoreStation.Instance?.storeEventManager;
if (mgr == null) return list;
- // 常规事件:开门时先过"今天有没有事件"(5 天后才开始,有 0/1/2 件在身上时 50/35/25%),再按权重抽
- int counter = mgr.GetEventCounter();
- double dayChance = counter == 0 ? 0.50 : counter == 1 ? 0.35 : counter == 2 ? 0.25 : 0;
+ double dayChance = DayChance(mgr);
var pool = StoreEventManager.normalEventBlueprints.Where(e => !mgr.IsEventOnCooldown(e.identifier)).ToList();
double total = pool.Sum(e => (double)e.odd);
foreach (var bp in pool)
@@ -51,7 +94,7 @@ namespace Decentralized
}
foreach (var f in Factions)
{
- if (mgr.GetActiveEvents().Any(e => e.isFactionOperation && e.sourceFaction == f)) continue;
+ if (FactionBusy(mgr, f)) continue;
list.Add(new PredictCandidate { Kind = PredictKind.Faction, Key = f, Title = L.T("predict.faction." + f), Prob = (from, to) => PredictMath.FactionProb(to - from + 1) });
}
foreach (var t in GutterCuts)
@@ -70,19 +113,68 @@ namespace Decentralized
double thr = Math.Round(price * mult, price >= 100 ? 0 : 1);
list.Add(new PredictCandidate { Kind = PredictKind.Price, Key = "PSC", Title = "PSC", Param = thr, Prob = (from, to) => PredictMath.PriceAtLeast(price, anchor, Tuning.PscVol, Tuning.Revert, to - day, thr) });
}
- // 价格盘一次只留一个(阈值不同的盘口会互相对冲)
+ if (d.GasPhase >= 0)
+ foreach (var t in GasCuts)
+ {
+ double thr = t;
+ list.Add(new PredictCandidate { Kind = PredictKind.Gas, Key = "gas", Title = "Gas", Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.GasFlat(day + 1, thr) });
+ }
+ if (d.Stable.CraWarnDay < 0 && d.Stable.CraSpiralDay < 0)
+ list.Add(new PredictCandidate { Kind = PredictKind.Cra, Key = "cra", Title = "CRA", Prob = (from, to) => PredictMath.CraWarnProb(d.Stable.CraSubsidy, CraDrain(d), day, from, to, CraHazard) });
+ if (!d.Network.EraDone && d.Network.EraDay > 0 && day + 4 >= (d.Network.EraWarned ? d.Network.EraWarnDay : Tuning.EraMin))
+ list.Add(new PredictCandidate { Kind = PredictKind.Era, Key = "era", Title = L.T("predict.era"), Prob = (from, to) => PredictMath.EraProb(day, from, to, d.Network.EraWarned ? d.Network.EraWarnDay : -1) });
return list;
}
- /// 开门后补盘。
+ private static double CraHazard => 1 - Math.Pow(1 - Tuning.CraSpiralWeekly, 1.0 / 7);
+ private static double CraDrain(SaveData d) => (Tuning.CraNpcStake + d.Staking.Earning) * d.Stable.CraRate;
+
+ /// 开门后补盘。价格盘一次只留一个(阈值不同的盘口会互相对冲)。
public static void Morning(SaveData d, int day)
{
if (!Available(day)) return;
- var made = d.Predict.Refill(day, d.Rng, Candidates(day).Where(c => c.Kind != PredictKind.Price || !d.Predict.Markets.Any(m => m.Outcome < 0 && m.Kind == PredictKind.Price)).ToList());
+ var made = d.Predict.Refill(day, d.Rng, Candidates(day));
foreach (var m in made) Log.Info($"预测盘口 #{m.Id}:{m.Kind} {m.Key} {m.Title} 阈值 {m.Param} 窗口 {m.FromDay}-{m.ToDay} 先验 {m.P0:0.000}");
}
- /// 夜间结算的结果:事件、派系读观测;纯度读当天的数值;行情读夜间更新之前的价格。
+ // ——— 公开信息下的合理概率(盘口夜里向它靠拢) ———
+
+ ///
+ /// 市场里的 NPC 看得到的东西:报纸上已经排期的事件、正在进行的事件与派系行动、冷却、当前行情、gas 的周期、补贴池、矿机时代预警。
+ /// 看不到:没上报纸的排期(传闻、派系特权之外)、减半抬价和排期好的镇压对价格锚的未来影响。day 是今晚。
+ ///
+ public static double? PublicProb(PredictMarket m, SaveData d, int day)
+ {
+ var mgr = StoreStation.Instance?.storeEventManager;
+ int days = m.ToDay - m.FromDay + 1;
+ switch (m.Kind)
+ {
+ case PredictKind.Event:
+ if (mgr == null) return null;
+ if (QueuedStart(m, true) != null) return 0.92;
+ if (mgr.IsEventActive(m.Key) || mgr.IsEventOnCooldown(m.Key)) return 0.02;
+ return EventProb(mgr, m.Key, days);
+ case PredictKind.Faction:
+ if (mgr == null) return null;
+ if (QueuedStart(m, true) != null) return 0.92;
+ if (FactionBusy(mgr, m.Key)) return 0.02;
+ return PredictMath.FactionProb(days);
+ case PredictKind.Price:
+ return PredictMath.PriceAtLeast(d.Market.Price, d.Market.Anchor(d.ActiveEvents), Tuning.PscVol, Tuning.Revert, Math.Max(1, m.ToDay - day), m.Param);
+ case PredictKind.Gas:
+ return d.GasPhase < 0 ? (double?)null : PredictMath.GasAtLeast(m.ToDay, d.GasPhase, m.Param);
+ case PredictKind.Cra:
+ if (d.Stable.CraWarnDay >= 0 || d.Stable.CraSpiralDay >= 0) return 0.02;
+ return PredictMath.CraWarnProb(d.Stable.CraSubsidy, CraDrain(d), day + 1, m.FromDay, m.ToDay, CraHazard);
+ case PredictKind.Era:
+ return PredictMath.EraProb(day, m.FromDay, m.ToDay, d.Network.EraWarned ? d.Network.EraWarnDay : -1);
+ }
+ return null;
+ }
+
+ // ——— 结算 ———
+
+ /// 夜间结算的结果:事件、派系读观测;纯度读当天的数值;行情读夜间更新之前的价格;gas 读当天的指数;CRA 预警与矿机时代读 mod 状态(要在这两项夜间逻辑之后结算)。
public static bool? Outcome(PredictMarket m, SaveData d)
{
switch (m.Kind)
@@ -91,27 +183,33 @@ namespace Decentralized
case PredictKind.Purity:
var st = StoreStation.Instance;
return st == null ? (bool?)null : (m.Key == "gutter" ? st.todayGutterflowPurity : st.todayRustwaterPurity) >= m.Param;
- case PredictKind.Price: return d.Market.Price >= m.Param;
+ case PredictKind.Price: return ClosePrice >= m.Param;
+ case PredictKind.Gas: return CloseGas >= m.Param;
+ case PredictKind.Cra: return d.Stable.CraWarnDay >= m.FromDay && d.Stable.CraWarnDay <= m.ToDay;
+ case PredictKind.Era: return d.Network.EraDay < 0 ? (bool?)null : d.Network.EraDay >= m.FromDay && d.Network.EraDay <= m.ToDay;
}
return null;
}
- /// 下注时玩家是否已经知道答案:这个事件(或派系行动)已经排进日程、玩家看得到(传闻纸条、派系特权),而且落在窗口里。只有押"会发生"才算内幕单。
+ // ——— 情报 ———
+
+ /// 这个盘口的真相是不是已经定了(情报贩子只能卖已经定了的):派系行动看排期(包括没上报纸的),矿机时代看 mod 自己掷出的日子。
+ public static bool? Truth(PredictMarket m)
+ {
+ var d = Session.Data;
+ if (m.Kind == PredictKind.Faction) return QueuedStart(m, false) != null;
+ if (m.Kind == PredictKind.Era) return d.Network.EraDay > 0 ? d.Network.EraDay >= m.FromDay && d.Network.EraDay <= m.ToDay : (bool?)null;
+ return null;
+ }
+
+ public static bool Tippable(PredictMarket m, int day) => m.IsOpen(day) && Truth(m) != null && (m.Kind != PredictKind.Faction || m.ToDay <= day + 4);
+
+ /// 下注时玩家是否已经知道答案:这个事件(或派系行动)已经以玩家看得到的形式排进日程(传闻纸条、派系特权),或者买了情报贩子的内幕消息且说的就是这一边。
public static bool Known(PredictMarket m, bool yes)
{
- if (!yes || (m.Kind != PredictKind.Event && m.Kind != PredictKind.Faction)) return false;
- var mgr = StoreStation.Instance?.storeEventManager;
- if (mgr == null) return false;
- int today = StoreStation.GetDayCounter();
- for (int i = 0; i < mgr.futurEvents.Count; i++)
- {
- var e = mgr.futurEvents[i];
- if (!e.IsKnown) continue;
- bool match = m.Kind == PredictKind.Event ? e.identifier == m.Key : e.isFactionOperation && e.sourceFaction == m.Key;
- int start = today + mgr.futurEventsDay[i];
- if (match && start >= m.FromDay && start <= m.ToDay) return true;
- }
- return false;
+ var tip = Session.Data?.Predict.TipFor(m.Id);
+ if (tip != null && tip.Claim == yes) return true;
+ return yes && QueuedStart(m, true) != null;
}
public static string Question(PredictMarket m)
@@ -122,8 +220,127 @@ namespace Decentralized
case PredictKind.Event: return L.T("predict.q.event", win, m.Title);
case PredictKind.Faction: return L.T("predict.q.faction", win, m.Title);
case PredictKind.Purity: return L.T("predict.q.purity", win, m.Title, Fmt.Num(m.Param));
+ case PredictKind.Gas: return L.T("predict.q.gas", win, Fmt.Num(m.Param));
+ case PredictKind.Cra: return L.T("predict.q.cra", win);
+ case PredictKind.Era: return L.T("predict.q.era", win);
default: return L.T("predict.q.price", win, m.Title, Fmt.Num(m.Param));
}
}
+
+ // ——— 线索 ———
+
+ /// 盘口面板里的"线索":玩家手里已有的、和这个盘口有关的信息。每条尽量短。
+ public static List Clues(PredictMarket m)
+ {
+ var d = Session.Data;
+ var list = new List();
+ var mgr = StoreStation.Instance?.storeEventManager;
+ int today = StoreStation.GetDayCounter();
+ var tip = d.Predict.TipFor(m.Id);
+ if (tip != null) list.Add(L.T("predict.clue.tip", L.T("predict.tier." + tip.Tier), L.T(tip.Claim ? "predict.yes" : "predict.no")));
+ switch (m.Kind)
+ {
+ case PredictKind.Event:
+ {
+ int? s = QueuedStart(m, true);
+ if (s != null) list.Add(L.T("predict.clue.queued", s.Value));
+ else if (mgr != null && (mgr.IsEventActive(m.Key) || mgr.IsEventOnCooldown(m.Key))) list.Add(L.T("predict.clue.cool"));
+ break;
+ }
+ case PredictKind.Faction:
+ {
+ int? s = QueuedStart(m, true);
+ if (s != null) list.Add(L.T("predict.clue.queued", s.Value));
+ else if (mgr != null && FactionBusy(mgr, m.Key)) list.Add(L.T("predict.clue.busy"));
+ break;
+ }
+ case PredictKind.Price:
+ {
+ int toHalv = Tuning.HalvingDays - today % Tuning.HalvingDays;
+ if (toHalv <= m.ToDay - today + Tuning.HalvingRampDays) list.Add(L.T("predict.clue.halving", today + toHalv));
+ if (mgr != null)
+ for (int i = 0; i < mgr.futurEvents.Count; i++)
+ {
+ var e = mgr.futurEvents[i];
+ if (e.IsKnown && (e.identifier == "crackdown" || e.identifier == "stationWideBlackout" || e.identifier == "powerShortage"))
+ list.Add(L.T("predict.clue.queued2", today + mgr.futurEventsDay[i], e.GetDisplayName()));
+ }
+ list.Add(L.T("predict.clue.anchor", Fmt.Num(d.Market.Anchor(d.ActiveEvents))));
+ break;
+ }
+ case PredictKind.Gas:
+ if (d.GasHist.Count > 0) list.Add(L.T("predict.clue.gas", string.Join(" ", d.GasHist.Skip(Math.Max(0, d.GasHist.Count - 7)).Select(x => x.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture)))));
+ break;
+ case PredictKind.Cra:
+ list.Add(L.T("predict.clue.subsidy", (d.Stable.CraSubsidy / Tuning.CraSubsidy0 * 100).ToString("0")));
+ list.Add(L.T("predict.clue.drain", Fmt.Num(CraDrain(d))));
+ break;
+ case PredictKind.Era:
+ list.Add(d.Network.EraWarned ? L.T("predict.clue.era.warned", d.Network.EraWarnDay + Tuning.EraWarnMin, d.Network.EraWarnDay + Tuning.EraWarnMax) : L.T("predict.clue.era", Tuning.EraMin, Tuning.EraMax));
+ break;
+ }
+ return list;
+ }
+
+ // ——— 局势简报 ———
+
+ private static string OpName(string id)
+ {
+ var bp = StoreOperationManager.operationBlueprints.Find(b => b.identifier == id);
+ try { return bp != null ? bp.storeEventFunc().GetDisplayName() : id; } catch (Exception) { return id; }
+ }
+
+ /// 按游戏的阈值列出每个派系现在能发起的行动。fake:假情报,数值是编的。
+ public static List MakeBrief(bool fake, int day)
+ {
+ var mgr = StoreStation.Instance?.storeEventManager;
+ var om = StoreStation.Instance?.storeOperationManager;
+ var lines = new List();
+ if (om == null || mgr == null) return lines;
+ int sec = om.secPower, rev = om.revPower, bm = om.blackMarketPower, ll = om.lowerLevelUnrest, ul = om.upperLevelFriendliness;
+ if (fake)
+ {
+ var rng = Session.Data.Rng;
+ sec = rng.Range(0, 100); rev = rng.Range(0, 100); bm = rng.Range(0, 100); ll = rng.Range(0, 100); ul = rng.Range(0, 100);
+ }
+ lines.Add(L.T("predict.brief.levels", sec, rev, bm, ll, ul));
+ Func> ops = f =>
+ {
+ var l = new List();
+ switch (f)
+ {
+ case "FACTION_SECURITY":
+ if (sec >= 20 && bm >= 40) l.Add("crackdown");
+ if (sec >= 20 && (rev >= 33 || ll >= 33 || bm >= 33)) l.Add("sec_increase_patrols");
+ if (sec >= 15 && rev >= 40) l.Add("sec_raid_revolution");
+ if (sec >= 15 && bm >= 40) l.Add("sec_raid_black_market");
+ if (sec >= 50 && (rev >= 65 || ll >= 65 || bm >= 65)) l.Add("sec_martial_law");
+ if (sec <= 5) l.Add("sec_budget_increase");
+ break;
+ case "FACTION_REVOLUTION":
+ if (day > 7) { if (rev <= 25) l.Add("rev_mobilization_secret"); if (rev >= 50) l.Add("rev_mobilization"); if (rev >= 15) l.Add("rev_raid_upper_level"); if (rev >= 25) l.Add("raid_security"); }
+ break;
+ case "FACTION_LOWER_LEVEL":
+ if (ll >= 85) l.Add("ll_riot"); if (ll >= 50) l.Add("ll_worker_strike");
+ break;
+ case "FACTION_UPPER_LEVEL":
+ if (ul >= 50 && ll >= 50) l.Add("ul_medical_aid"); if (ul >= 50 && ll <= 35) l.Add("ul_household_relief");
+ break;
+ case "FACTION_BLACK_MARKET":
+ if (day > 9) { if (bm >= 85) l.Add("bm_gang_war"); l.Add("bm_illegal_weapon_shipment"); }
+ break;
+ }
+ return l;
+ };
+ foreach (var f in Factions)
+ {
+ string name = L.T("predict.faction." + f);
+ if (FactionBusy(mgr, f)) { lines.Add(L.T("predict.brief.busy", name)); continue; }
+ if (mgr.futurEvents.Any(e => e.isFactionOperation && e.sourceFaction == f)) { lines.Add(L.T("predict.brief.queued", name)); continue; }
+ var l = ops(f);
+ lines.Add(l.Count == 0 ? L.T("predict.brief.none", name) : L.T("predict.brief.can", name, string.Join("、", l.Select(OpName))));
+ }
+ return lines;
+ }
}
}
diff --git a/src/Decentralized/Session.cs b/src/Decentralized/Session.cs
index 467c211..b44c01d 100644
--- a/src/Decentralized/Session.cs
+++ b/src/Decentralized/Session.cs
@@ -35,6 +35,10 @@ namespace Decentralized
public PerpBook Perps = new PerpBook();
/// 预测市场:盘口、持仓、观测记录(下注资金用 Poinbase 账户的 CRC)。
public PredictBook Predict = new PredictBook();
+ /// 近几天的 gas 指数(每晚记明天的),情报线索里显示;最近一份局势简报。
+ public List GasHist = new List();
+ public List Brief = new List();
+ public int BriefDay = -1;
/// 质押日息下调的新闻已经发过。
public bool CraLowNews;
public Network Network = new Network();
diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs
index 8112ee9..fdc76ec 100644
--- a/src/Decentralized/TerminalWindow.cs
+++ b/src/Decentralized/TerminalWindow.cs
@@ -350,10 +350,27 @@ namespace Decentralized
}
private static int _predPick;
+ private enum PredTab { Markets, Intel }
+ private static PredTab _predTab = PredTab.Markets;
- /// 预测页:左边盘口列表和最近结果,右边选中盘口的买卖。下注和卖出是链上合约调用,用当前钱包的 CRC,要在钱包上签名;持仓记在签名的钱包名下。
+ private static string P2(double x) => x.ToString("0.00", CultureInfo.InvariantCulture);
+
+ /// 预测页:盘口(左边列表与最近结果,右边选中盘口的价格、线索、买卖)和情报(情报贩子)。下注和卖出是链上合约调用,用当前钱包的 CRC,要在钱包上签名;持仓记在签名的钱包名下。
private static void PredictPage(CustomUIBuilder b)
{
+ if (_predTab == PredTab.Intel && !Unlock.BlackUnlocked) _predTab = PredTab.Markets;
+ if (Unlock.BlackUnlocked)
+ {
+ b.BeginRow();
+ foreach (PredTab t in Enum.GetValues(typeof(PredTab)))
+ {
+ var tab = t;
+ string name = L.T("predict.tab." + t.ToString().ToLowerInvariant());
+ b.AddButton(tab == _predTab ? $"[{name}]" : name, () => { _predTab = tab; _message = null; Open(); });
+ }
+ b.End();
+ }
+ if (_predTab == PredTab.Intel) { IntelPage(b); return; }
var d = Session.Data;
var bk = d.Predict;
var w = d.Hot;
@@ -376,7 +393,7 @@ namespace Decentralized
l.AddButton(m.Id == _predPick ? "[" + q + "]" : q, () => { _predPick = mk.Id; _message = null; Open(); });
l.End();
string state = m.IsOpen(day) ? "" : " " + L.T("predict.closed");
- l.AddLabel(L.T("predict.row", (m.PYes * 100).ToString("0"), ((1 - m.PYes) * 100).ToString("0")) + (all.Count > 0 ? " " + L.T("predict.held", L.T(all[0].Yes ? "predict.yes" : "predict.no"), Fmt.Num(all.Sum(x => x.Cost)), Fmt.Num(all.Sum(x => x.Shares))) : "") + state);
+ l.AddLabel(L.T("predict.row", P2(m.PYes), P2(1 - m.PYes)) + (all.Count > 0 ? " " + L.T("predict.held", L.T(all[0].Yes ? "predict.yes" : "predict.no"), Fmt.Num(all.Sum(x => x.Shares)), Fmt.Num(all.Sum(x => x.Cost))) : "") + state);
}
if (bk.Recent.Count > 0)
{
@@ -389,14 +406,19 @@ namespace Decentralized
if (sel == null) { r.AddHeader(L.T("predict.h")); return; }
r.AddHeader(L.T("predict.pick"));
r.AddLabel(PredictGame.Question(sel));
- r.AddLabel(L.T("predict.price", (sel.PYes * 100).ToString("0.0"), ((1 - sel.PYes) * 100).ToString("0.0")));
+ r.AddLabel(L.T("predict.price", P2(sel.PYes), P2(1 - sel.PYes)));
+ foreach (var clue in PredictGame.Clues(sel)) r.AddLabel("· " + clue);
foreach (var pos in bk.PosAll(sel.Id))
{
var ow = d.Own(pos.Wallet);
r.AddLabel(L.T("predict.pos", ow != null ? WalletWindow.NameOf(ow) : pos.Wallet, L.T(pos.Yes ? "predict.yes" : "predict.no"), Fmt.Num(pos.Shares)));
+ if (pos.Wallet == w.Address)
+ {
+ r.AddLabel(L.T("predict.pos3", P2(pos.Cost / pos.Shares), P2(sel.PriceOf(pos.Yes))));
+ r.AddLabel(L.T("predict.pos2", Fmt.Num(pos.Cost), Fmt.Num(pos.Shares)));
+ r.AddLabel(L.T("predict.pos4", Fmt.Num(sel.SellValue(pos.Yes, pos.Shares) * (1 - Tuning.PredictFee))));
+ }
}
- var mine = bk.PosOf(sel.Id, w.Address);
- if (mine != null) r.AddLabel(L.T("predict.pos2", Fmt.Num(mine.Cost), Fmt.Num(sel.SellValue(mine.Yes, mine.Shares) * (1 - Tuning.PredictFee))));
if (!sel.IsOpen(day)) { r.AddLabel(L.T("predict.closed.note")); return; }
AmountRow(r);
if (double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double v) && v > 0 && v < 1e7)
@@ -404,7 +426,7 @@ namespace Decentralized
foreach (bool yes in new[] { true, false })
{
double shares = sel.SharesFor(yes, v / (1 + Tuning.PredictFee));
- r.AddLabel(L.T("predict.preview", L.T(yes ? "predict.yes" : "predict.no"), Fmt.Num(shares), Fmt.Num(shares - v)));
+ r.AddLabel(L.T("predict.preview", L.T(yes ? "predict.yes" : "predict.no"), Fmt.Num(shares), P2(v / shares), Fmt.Num(shares - v)));
}
}
r.BeginRow();
@@ -415,6 +437,97 @@ namespace Decentralized
});
}
+ /// 情报页:局势简报,以及对派系行动、矿机时代盘口的内幕消息。付款是 CRT 的合约调用(销毁),要签名。
+ private static void IntelPage(CustomUIBuilder b)
+ {
+ var d = Session.Data;
+ var w = d.Hot;
+ int day = StoreStation.GetDayCounter();
+ Columns(b, l =>
+ {
+ l.AddHeader(L.T("intel.h"));
+ if (!PredictGame.BrokerOpen) { l.AddLabel(L.T("intel.locked", Tuning.IntelRep)); return; }
+ l.AddLabel(L.T("predict.acct.crt", WalletWindow.NameOf(w), Fmt.Num(w.Avail(Assets.CRT))));
+ l.AddButton(L.T("intel.brief.buy", Fmt.Num(Tuning.IntelBriefPrice)), IntelBuyBrief);
+ l.AddSeparator();
+ if (d.Brief.Count == 0) l.AddLabel(L.T("intel.brief.none"));
+ else
+ {
+ l.AddLabel(L.T("intel.brief.day", d.BriefDay));
+ foreach (var line in d.Brief) l.AddLabel(line);
+ }
+ }, r =>
+ {
+ r.AddHeader(L.T("intel.tips.h"));
+ if (!PredictGame.BrokerOpen) return;
+ var list = d.Predict.Markets.Where(m => PredictGame.Tippable(m, day)).OrderBy(m => m.FromDay).ToList();
+ if (list.Count == 0) r.AddLabel(L.T("intel.tips.none"));
+ foreach (var m in list)
+ {
+ var mk = m;
+ r.AddLabel(PredictGame.Question(m));
+ var tip = d.Predict.TipFor(m.Id);
+ if (tip != null) { r.AddLabel(L.T("predict.clue.tip", L.T("predict.tier." + tip.Tier), L.T(tip.Claim ? "predict.yes" : "predict.no"))); continue; }
+ for (int t = 0; t < 3; t++)
+ {
+ int tier = t;
+ r.AddButton(L.T("intel.tip.buy", (int)Math.Round(Tuning.IntelTipAcc[t] * 100), Fmt.Num(Tuning.IntelTipPrice[t] * m.MaxLoss)), () => IntelBuyTip(mk, tier));
+ }
+ }
+ });
+ }
+
+ private static void IntelBuyBrief()
+ {
+ var d = Session.Data;
+ double price = Tuning.IntelBriefPrice;
+ if (d.Hot.Avail(Assets.CRT) + 1e-9 < price) { _message = L.T("intel.err.funds"); Open(); return; }
+ _message = L.T("hw.wait");
+ Open();
+ WalletWindow.OpenForBill(new WalletWindow.Bill
+ {
+ Contract = true, Asset = Assets.CRT, Amount = price, Summary = L.T("intel.brief.sum", Fmt.Num(price)),
+ Execute = pw =>
+ {
+ if (pw.Avail(Assets.CRT) + 1e-9 < price) { _message = L.T("intel.err.funds"); Open(); return; }
+ pw.Spend(Assets.CRT, price, Session.Price);
+ int day = StoreStation.GetDayCounter();
+ bool fake = d.Rng.Chance(Tuning.IntelFake);
+ d.Brief = PredictGame.MakeBrief(fake, day); d.BriefDay = day;
+ Log.Info($"局势简报:{(fake ? "假情报" : "真")},{string.Join(" | ", d.Brief)}");
+ _message = L.T("intel.brief.done");
+ Session.Save();
+ Open();
+ },
+ });
+ }
+
+ private static void IntelBuyTip(PredictMarket m, int tier)
+ {
+ var d = Session.Data;
+ double price = Tuning.IntelTipPrice[tier] * m.MaxLoss;
+ if (d.Hot.Avail(Assets.CRT) + 1e-9 < price) { _message = L.T("intel.err.funds"); Open(); return; }
+ _message = L.T("hw.wait");
+ Open();
+ WalletWindow.OpenForBill(new WalletWindow.Bill
+ {
+ Contract = true, Asset = Assets.CRT, Amount = price, Summary = L.T("intel.tip.sum", Fmt.Num(price), PredictGame.Question(m)),
+ Execute = pw =>
+ {
+ int day = StoreStation.GetDayCounter();
+ var truth = PredictGame.Truth(m);
+ if (pw.Avail(Assets.CRT) + 1e-9 < price || truth == null || !m.IsOpen(day) || d.Predict.TipFor(m.Id) != null) { _message = L.T("intel.err.funds"); Open(); return; }
+ pw.Spend(Assets.CRT, price, Session.Price);
+ var tip = PredictBook.MakeTip(m, truth.Value, tier, d.Rng);
+ d.Predict.Tips.Add(tip);
+ Log.Info($"内幕消息 #{m.Id} 第 {tier + 1} 档:说{(tip.Claim ? "是" : "否")},真相{(truth.Value ? "是" : "否")}");
+ _message = L.T("intel.tip.done", L.T(tip.Claim ? "predict.yes" : "predict.no"));
+ Session.Save();
+ Open();
+ },
+ });
+ }
+
private static void PredictBuy(PredictMarket m, bool yes)
{
if (!ReadAmount(out double v)) return;
diff --git a/tests/Decentralized.Domain.Tests/Program.cs b/tests/Decentralized.Domain.Tests/Program.cs
index 304a3c1..30023a0 100644
--- a/tests/Decentralized.Domain.Tests/Program.cs
+++ b/tests/Decentralized.Domain.Tests/Program.cs
@@ -1190,6 +1190,55 @@ internal static class Program
Check(caught > 15 && caught < 45, $"分到两个钱包的内幕持仓合计后约一半被查:{caught}/60");
});
+ Test("预测市场:夜间消息反应、情报、新盘口先验", () =>
+ {
+ var rng = new Rng(9);
+ var bk = new PredictBook();
+ var m = PredictMarket.Create(++bk.Seq, PredictKind.Event, "e", "t", 0, 5, 8, 9, 0.1); bk.Markets.Add(m);
+ double sum = 0; int n = 400;
+ for (int i = 0; i < n; i++) { m.QYes = m.QNo + m.B * Math.Log(0.1 / 0.9); bk.Drift(5, x => 0.9, rng); sum += m.PYes; }
+ Check(sum / n > 0.4 && sum / n < 0.6, $"一晚把 10% 往 90% 拉近约一半:{sum / n:0.00}");
+ var closing = PredictMarket.Create(++bk.Seq, PredictKind.Event, "e", "t", 0, 5, 6, 6, 0.1); bk.Markets.Add(closing);
+ double before = closing.PYes; bk.Drift(5, x => 0.9, rng);
+ Check(Math.Abs(closing.PYes - before) < 1e-12, "明天封盘的盘口不再反应");
+ Check(m.PYes >= Tuning.PredictPMin && m.PYes <= Tuning.PredictPMax, "价格夹在范围内");
+ // 反应是有噪声的:同一目标多次结果不全一样
+ var set = new System.Collections.Generic.HashSet();
+ for (int i = 0; i < 20; i++) { m.QYes = m.QNo + m.B * Math.Log(0.1 / 0.9); bk.Drift(5, x => 0.5, rng); set.Add(Math.Round(m.PYes, 4)); }
+ Check(set.Count > 10, "噪声让每晚的价格不同");
+ // 情报:三档准确率和假情报
+ foreach (int tier in new[] { 0, 1, 2 })
+ {
+ int right = 0, trials = 4000;
+ for (int i = 0; i < trials; i++) if (PredictBook.MakeTip(m, true, tier, rng).Claim) right++;
+ double expect = (1 - Tuning.IntelFake) * Tuning.IntelTipAcc[tier] + Tuning.IntelFake * 0;
+ Check(Math.Abs(right / (double)trials - expect) < 0.04, $"第 {tier + 1} 档说对的比例约 {expect:0.00}:{right / (double)trials:0.00}");
+ }
+ Check(Math.Abs(PredictBook.MakeTip(m, true, 2, rng).Paid - Tuning.IntelTipPrice[2] * m.MaxLoss) < 1e-9, "价格按盘口最大亏损的比例");
+ // 到期后消息一起清掉
+ bk.Tips.Add(new PredictTip { Market = closing.Id });
+ var w = new Wallet { Address = "x" };
+ bk.Night(6, new string[0], mm => true, _ => w, rng);
+ Check(bk.Tips.Count == 0, "盘口结算后情报作废");
+ // gas / CRA / 时代 先验
+ double flat = PredictMath.GasFlat(10, 1.2);
+ double phase0 = PredictMath.GasAtLeast(10, 0, 1.2), sum2 = 0;
+ for (int ph = 0; ph < Tuning.TxGasPeriod; ph++) sum2 += PredictMath.GasAtLeast(10, ph, 1.2);
+ Check(Math.Abs(flat - sum2 / Tuning.TxGasPeriod) < 1e-12 && phase0 >= 0 && phase0 <= 1, "gas 先验是各相位的平均");
+ Check(PredictMath.GasAtLeast(10, 0, 0.1) == 1 && PredictMath.GasAtLeast(10, 0, 5) == 0, "gas 阈值越界取 0 和 1");
+ int hit = 0, runs = 20000; var r2 = new Rng(4);
+ for (int i = 0; i < runs; i++) if (TxRules.Gas(10, 3, r2) >= 1.3) hit++;
+ Check(Math.Abs(hit / (double)runs - PredictMath.GasAtLeast(10, 3, 1.3)) < 0.02, "gas 概率和真实抽样对得上");
+ double h = 1 - Math.Pow(1 - Tuning.CraSpiralWeekly, 1.0 / 7);
+ Check(Math.Abs(PredictMath.CraWarnProb(1e9, 100, 10, 12, 14, h) - (1 - Math.Pow(1 - h, 3)) * Math.Pow(1 - h, 2)) < 1e-9, "补贴池充足时按日概率,窗口前发出的不算");
+ Check(PredictMath.CraWarnProb(150, 100, 10, 11, 12, h) > 0.99, "补贴池撑不过窗口前一晚就必发");
+ Check(PredictMath.CraWarnProb(50, 100, 10, 10, 10, h) == 1, "归零那晚必发");
+ Check(PredictMath.EraProb(60, 61, 63, -1) > 0 && PredictMath.EraProb(60, 61, 63, -1) < 0.2, "没预警时在 50–90 里均匀,3 天窗口很小");
+ Check(Math.Abs(PredictMath.EraProb(60, 62, 63, 58) - 0.5) < 1e-9 || PredictMath.EraProb(60, 62, 63, 58) > 0, "预警后落在 4–7 天内");
+ Check(PredictMath.EraProb(60, 100, 105, -1) == 0, "窗口在时代区间之外为 0");
+ Check(PredictMath.EraProb(60, 62, 65, 58) > 0.5 && PredictMath.EraProb(60, 70, 71, 58) == 0, "预警后窗口覆盖区间就是高概率");
+ });
+
Test("预测市场:先验概率", () =>
{
Check(Math.Abs(PredictMath.PurityAtLeast(8500, 8000, 8999) - 0.5) < 1e-9, "纯度 8500 以上一半");