Stage 7.1: prediction markets (LMSR, event/faction/purity/price markets, insider check)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7d13ae2fe8
commit
e5a6e3e22c
11 files changed
+810
-4
No files matched your search
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Decentralized.Domain;
|
||||
|
||||
namespace Decentralized
|
||||
{
|
||||
/// <summary>
|
||||
/// 预测市场的接入层:按游戏当前状态给出候选盘口,夜间读游戏事件、派系行动、水纯度和本 mod 价格来结算,判断下注时玩家是否已经知道答案。
|
||||
/// </summary>
|
||||
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 };
|
||||
|
||||
public static bool Available(int day) => Session.Data != null && Unlock.IsUnlocked && day >= Tuning.PredictUnlockDay;
|
||||
|
||||
/// <summary>夜间观测用的标识:生效中的事件,以及派系行动的 "F:派系"。</summary>
|
||||
public static List<string> ActiveIds()
|
||||
{
|
||||
var list = new List<string>();
|
||||
var mgr = StoreStation.Instance?.storeEventManager;
|
||||
if (mgr == null) return list;
|
||||
foreach (var e in mgr.GetActiveEvents())
|
||||
{
|
||||
list.Add(e.identifier);
|
||||
if (e.isFactionOperation && !string.IsNullOrEmpty(e.sourceFaction)) list.Add("F:" + e.sourceFaction);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static List<PredictCandidate> Candidates(int day)
|
||||
{
|
||||
var d = Session.Data;
|
||||
var list = new List<PredictCandidate>();
|
||||
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;
|
||||
var pool = StoreEventManager.normalEventBlueprints.Where(e => !mgr.IsEventOnCooldown(e.identifier)).ToList();
|
||||
double total = pool.Sum(e => (double)e.odd);
|
||||
foreach (var bp in pool)
|
||||
{
|
||||
if (mgr.IsEventActive(bp.identifier)) continue;
|
||||
string name;
|
||||
try { name = bp.storeEventFunc().GetDisplayName(); } catch (Exception) { continue; }
|
||||
double odd = bp.odd;
|
||||
list.Add(new PredictCandidate { Kind = PredictKind.Event, Key = bp.identifier, Title = name, Prob = (from, to) => PredictMath.EventProb(dayChance, odd, total, to - from + 1) });
|
||||
}
|
||||
foreach (var f in Factions)
|
||||
{
|
||||
if (mgr.GetActiveEvents().Any(e => e.isFactionOperation && e.sourceFaction == 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)
|
||||
{
|
||||
double thr = t;
|
||||
list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "gutter", Title = L.T("predict.purity.gutter"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 8000, 8999) });
|
||||
}
|
||||
foreach (var t in RustCuts)
|
||||
{
|
||||
double thr = t;
|
||||
list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "rust", Title = L.T("predict.purity.rust"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 9000, 9150) });
|
||||
}
|
||||
double price = d.Market.Price, anchor = d.Market.Anchor(d.ActiveEvents);
|
||||
foreach (var mult in PriceMult)
|
||||
{
|
||||
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) });
|
||||
}
|
||||
// 价格盘一次只留一个(阈值不同的盘口会互相对冲)
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>开门后补盘。</summary>
|
||||
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());
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>夜间结算的结果:事件、派系读观测;纯度读当天的数值;行情读夜间更新之前的价格。</summary>
|
||||
public static bool? Outcome(PredictMarket m, SaveData d)
|
||||
{
|
||||
switch (m.Kind)
|
||||
{
|
||||
case PredictKind.Event: case PredictKind.Faction: return d.Predict.Happened(m);
|
||||
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;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>下注时玩家是否已经知道答案:这个事件(或派系行动)已经排进日程、玩家看得到(传闻纸条、派系特权),而且落在窗口里。只有押"会发生"才算内幕单。</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
public static string Question(PredictMarket m)
|
||||
{
|
||||
string win = m.FromDay == m.ToDay ? L.T("predict.win.one", m.FromDay) : L.T("predict.win.range", m.FromDay, m.ToDay);
|
||||
switch (m.Kind)
|
||||
{
|
||||
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));
|
||||
default: return L.T("predict.q.price", win, m.Title, Fmt.Num(m.Param));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user