鉴定改版:风扇数、供电口、丝印、序列号四项真伪依据在前,假卡依据各自独立(平均 1.5 项);指南去档次、去卖家说法

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 19:40:50 +08:00
1 parent a8d7bc3427
commit f40436b7d2
57 files changed
+451 -261

No files matched your search

+22 -11
View File
@@ -8,10 +8,14 @@ namespace Decentralized.Domain
{
public string Model;
public int Fan, Fingers, Bracket, Print, Sticker, Serial;
/// <summary>风扇数、供电口:0 表示由型号的档次决定(说法里写的型号该有的),否则是写明的值(风扇数 1 到 3;供电口 1 到 3,对应 0 到 2 加一)。</summary>
public int CountSet, PinSet;
public int Tier => Hardware.Specs[Model].Tier;
public int Count => CountSet != 0 ? CountSet : Hardware.FanCount[Tier];
public int Pin => PinSet != 0 ? PinSet - 1 : Hardware.PinClass[Tier];
public int Get(int row) => row == CardLabels.RowBuild ? Tier : row == CardLabels.RowFan ? Fan : row == CardLabels.RowFingers ? Fingers
public int Get(int row) => row == CardLabels.RowCount ? Count : row == CardLabels.RowPin ? Pin : row == CardLabels.RowFan ? Fan : row == CardLabels.RowFingers ? Fingers
: row == CardLabels.RowBracket ? Bracket : row == CardLabels.RowPrint ? Print : row == CardLabels.RowSerial ? Serial : Sticker;
public Claim With(int row, int value)
@@ -20,24 +24,26 @@ namespace Decentralized.Domain
if (row == CardLabels.RowFan) c.Fan = value; else if (row == CardLabels.RowFingers) c.Fingers = value;
else if (row == CardLabels.RowBracket) c.Bracket = value; else if (row == CardLabels.RowPrint) c.Print = value;
else if (row == CardLabels.RowSerial) c.Serial = value; else if (row == CardLabels.RowSeal) c.Sticker = value;
else if (row == CardLabels.RowCount) c.CountSet = value; else if (row == CardLabels.RowPin) c.PinSet = value + 1;
return c;
}
public string Encode() => $"{Model}:{Fan}{Fingers}{Bracket}{Print}{Sticker}{Serial}";
public string Encode() => $"{Model}:{Fan}{Fingers}{Bracket}{Print}{Sticker}{Serial}{CountSet}{PinSet}";
public static Claim Decode(string s)
{
var p = (s ?? "").Split(':');
if (p.Length != 2 || p[1].Length != 6 || !Hardware.Specs.ContainsKey(p[0])) return default;
if (p.Length != 2 || (p[1].Length != 6 && p[1].Length != 8) || !Hardware.Specs.ContainsKey(p[0])) return default;
int D(int i) => Math.Max(0, Math.Min(2, p[1][i] - '0'));
return new Claim { Model = p[0], Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4), Serial = D(5) };
int E(int i) => p[1].Length > i ? Math.Max(0, Math.Min(3, p[1][i] - '0')) : 0;
return new Claim { Model = p[0], Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4), Serial = D(5), CountSet = E(6), PinSet = E(7) };
}
/// <summary>按真实情况写出的说法(没有隐瞒)。</summary>
public static Claim Truth(Unit u)
{
var l = u.Appearance;
return new Claim { Model = u.FakeOf ?? u.Model, Fan = l.Fan, Fingers = l.Fingers, Bracket = l.Bracket, Print = l.Print, Sticker = l.Sticker, Serial = l.Serial };
return new Claim { Model = u.FakeOf ?? u.Model, Fan = l.Fan, Fingers = l.Fingers, Bracket = l.Bracket, Print = l.Print, Sticker = l.Sticker, Serial = l.Serial, CountSet = u.ObservedCount, PinSet = u.ObservedPin + 1 };
}
}
@@ -49,10 +55,12 @@ namespace Decentralized.Domain
/// </summary>
public static class CardLabels
{
public const int RowSpec = 1, RowBuild = 2, RowFan = 4, RowFingers = 8, RowBracket = 16, RowPrint = 32, RowSerial = 64, RowSeal = 128, AllRows = 255;
/// <summary>标签栏的顺序:第一行鉴定真伪(型号;依据是供电口与风扇数、序列号、背面丝印,玩家对着大图自己看),后四行鉴定成色。</summary>
public static readonly int[] Rows = { RowSpec, RowFan, RowFingers, RowBracket, RowSeal };
public const int AuthRows = 1;
public const int RowSpec = 1, RowCount = 2, RowFan = 4, RowFingers = 8, RowBracket = 16, RowPrint = 32, RowSerial = 64, RowSeal = 128, RowPin = 256, AllRows = 511;
/// <summary>标签栏的顺序:风扇数、供电口、背面丝印、序列号(真伪依据),型号,叶片、金手指、挡板、封条(成色)。</summary>
public static readonly int[] Rows = { RowCount, RowPin, RowPrint, RowSerial, RowSpec, RowFan, RowFingers, RowBracket, RowSeal };
/// <summary>前四行是真伪依据(风扇数、供电口、背面丝印、序列号),四项全部符合标称型号才是正品;第五行是型号,之后四行是成色。</summary>
public const int AuthRows = 4;
public static bool IsAuth(int row) => row == RowCount || row == RowPin || row == RowPrint || row == RowSerial;
/// <summary>各标签各取值对价格的影响(百分比,相加):风扇叶片积灰 −4%、缺角 −12%;金手指发暗 −4%、氧化 −12%;挡板划痕 −2%;封条缺失 −6%。
/// 型号、供电与风扇、丝印、序列号本身不扣价,它们决定的是这块卡是什么芯片。</summary>
@@ -73,7 +81,8 @@ namespace Decentralized.Domain
{
switch (row)
{
case RowBuild: return claim.Tier != real.Tier;
case RowCount: return claim.Count != real.Count;
case RowPin: return claim.Pin != real.Pin;
case RowPrint: return real.Print != 0 && claim.Print != real.Print;
case RowSerial: return real.Serial != 0 && claim.Serial != real.Serial;
}
@@ -108,7 +117,7 @@ namespace Decentralized.Domain
if (row == RowSpec) return claim.Model != real.Model ? 2 : 0;
if (IsEvidence(row, claim, real))
{
exposed |= RowSpec | RowBuild;
exposed |= RowSpec | RowCount | RowPin | RowPrint | RowSerial;
return 2;
}
if (wasExposed) return 0;
@@ -124,6 +133,7 @@ namespace Decentralized.Domain
{
if ((exposed & r) != 0) continue;
if (r == RowSpec) { if (claim.Model != real.Model) lies |= r; }
else if (IsAuth(r)) { if (claim.Get(r) != real.Get(r)) lies |= r; }
else if (claim.Get(r) != real.Get(r) && Pct(r, claim.Get(r)) > Pct(r, real.Get(r))) lies |= r;
}
return lies;
@@ -136,6 +146,7 @@ namespace Decentralized.Domain
if (kind == DealerKind.Honest) return c;
c.Model = u.Model;
double hide = kind == DealerKind.Scammer ? 1.0 : Tuning.UsedMiddlemanHide;
c.CountSet = 0; c.PinSet = 0;
foreach (int r in new[] { RowFan, RowFingers, RowBracket, RowPrint, RowSerial, RowSeal })
if (rng.Chance(hide)) c = c.With(r, 0);
return c;
+22 -8
View File
@@ -23,6 +23,11 @@ namespace Decentralized.Domain
/// 2 档次码与标称型号不符(贴的是原芯片的序列号)。后两种只出现在假卡上。</summary>
public int Serial;
/// <summary>风扇数对不上标称型号(实物是真实芯片的风扇数):0 对得上,1 对不上。只出现在假卡上。</summary>
public int CountBad;
/// <summary>供电口对不上标称型号(实物是真实芯片的供电口):0 对得上,1 对不上。只出现在假卡上。</summary>
public int PinBad;
public const int Variants = 3;
/// <summary>每张卡自己的随机种子:老化类维度(风扇、金手指、挡板划痕)由它和当前健康度决定,每晚损耗后重新算,
@@ -56,13 +61,14 @@ namespace Decentralized.Domain
public double HashFactor => FanHash[Fan] * FingerHash[Fingers];
/// <summary>六位数字,存在物品标签上:风扇、金手指、挡板、丝印、封条、序列号。</summary>
public string Encode() => $"{Fan}{Fingers}{Bracket}{Print}{Sticker}{Serial}";
public string Encode() => $"{Fan}{Fingers}{Bracket}{Print}{Sticker}{Serial}{CountBad}{PinBad}";
public static CardLook Decode(string s)
{
if (s == null || (s.Length != 5 && s.Length != 6)) return default;
if (s == null || s.Length < 5 || s.Length > 8) return default;
int D(int i) => Math.Max(0, Math.Min(Variants - 1, s[i] - '0'));
return new CardLook { Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4), Serial = s.Length > 5 ? D(5) : 0 };
int B(int i) => s.Length > i && s[i] == '1' ? 1 : 0;
return new CardLook { Fan = D(0), Fingers = D(1), Bracket = D(2), Print = D(3), Sticker = D(4), Serial = s.Length > 5 ? D(5) : 0, CountBad = B(6), PinBad = B(7) };
}
// 外观维度对应卡的真实状态:损耗、暴毙概率随之变化,价值走标签(见 CardLabels)
@@ -76,20 +82,28 @@ namespace Decentralized.Domain
public double DeathFactor => FanDeath[Fan] * FingerDeath[Fingers];
/// <summary>只在假卡上出现的取值:玩家一旦看到,就能肯定是假的。</summary>
public bool HasFakeSign => Print != 0 || Serial != 0;
public bool HasFakeSign => Print != 0 || Serial != 0 || CountBad != 0 || PinBad != 0;
/// <summary>
/// 生成外观。真卡:老化维度按健康度出现,封条缺失在二手卡上很常见,不会出现破绽类取值。
/// 假卡:在三类破绽(接口数、丝印、手写序列号)里随机挑 1–3 处,再叠加老化与封条缺失。
/// </summary>
public static CardLook Generate(Rng rng, double health, bool fake)
public static CardLook Generate(Rng rng, double health, bool fake, int labelTier = 3, int chipTier = 0)
{
var look = new CardLook { Seed = rng.NextUInt() | 1, Sticker = rng.Chance(fake ? 0.35 : 0.15 + 0.30 * (1 - Math.Max(0, Math.Min(100, health)) / 100.0)) ? 2 : 0 };
if (fake)
{
// 假卡本身就带着实物上的破绽(低档次的板子:供电口和风扇数对不上),此外骗子还可能重印丝印、补贴序列号
if (rng.Chance(0.55)) look.Print = rng.Chance(0.6) ? 1 : 2;
if (rng.Chance(0.65)) look.Serial = rng.Chance(0.55) ? 2 : 1;
// 四项真伪依据(风扇数、供电口、背面丝印、序列号)各自独立地露出破绽,至少一项;平均每张假卡约 1.5 项
bool countCan = Hardware.FanCount[chipTier] != Hardware.FanCount[labelTier], pinCan = Hardware.PinClass[chipTier] != Hardware.PinClass[labelTier];
double p = Tuning.FakeSignChance;
for (int tries = 0; tries < 30 && !look.HasFakeSign; tries++)
{
look.CountBad = countCan && rng.Chance(p) ? 1 : 0;
look.PinBad = pinCan && rng.Chance(p) ? 1 : 0;
look.Print = rng.Chance(p) ? (rng.Chance(0.6) ? 1 : 2) : 0;
look.Serial = rng.Chance(p) ? (rng.Chance(0.55) ? 2 : 1) : 0;
}
if (!look.HasFakeSign) look.Serial = 1;
}
return look.Appearance(health);
}
+7
View File
@@ -32,6 +32,8 @@ namespace Decentralized.Domain
/// <summary>各档次的实物特征:供电口针数(旗舰是两个 8 针)与风扇数量。</summary>
public static readonly string[] PowerPins = { "6", "8", "8", "8+8" };
public static readonly int[] FanCount = { 1, 2, 3, 3 };
/// <summary>各档次的供电口:0 = 6 针,1 = 8 针,2 = 双 8 针。</summary>
public static readonly int[] PinClass = { 0, 1, 1, 2 };
public const string Probidia = "Probidia", Pmd = "PMD", RatDigg = "RatDigg";
@@ -109,6 +111,11 @@ namespace Decentralized.Domain
public HardwareSpec Label => Hardware.Specs[Model];
public HardwareSpec Chip => Hardware.Specs[FakeOf ?? Model];
/// <summary>实物上的风扇数:对得上标称型号时是标称档次的,对不上(假卡的破绽)时是真实芯片的。</summary>
public int ObservedCount => Hardware.FanCount[(Look.CountBad != 0 ? Chip : Label).Tier];
/// <summary>实物上的供电口:0 = 6 针,1 = 8 针,2 = 双 8 针;规则同风扇数。</summary>
public int ObservedPin => Hardware.PinClass[(Look.PinBad != 0 ? Chip : Label).Tier];
/// <summary>当前算力:健康 ≥ 50 时满算力,低于 50 线性降到健康 0 时的 60%。</summary>
public double Hash(ModuleBonus b)
{
+2
View File
@@ -88,6 +88,8 @@ namespace Decentralized.Domain
public const int UsedFirstDay = 3;
/// <summary>显卡价值 = 型号定价 ×(下限 + (1 − 下限) × 健康度)。</summary>
public const double CardValueFloor = 0.25;
/// <summary>假卡的四项真伪依据(风扇数、供电口、背面丝印、序列号)各自露出破绽的概率(至少露出一项);约对应平均 1.5 项。</summary>
public const double FakeSignChance = 0.27;
/// <summary>黑市、鉴定等处二手卡的健康度范围(均匀取)。</summary>
public const double CardHealthMin = 25, CardHealthMax = 85;
/// <summary>二手贩子带来的卡的健康度范围,按老实人、中间商、骗子:越不老实卡越旧,平均每张卡约 0.5 / 1.5 / 2.5 项成色问题(叶片、金手指、挡板、封条)。</summary>