Cards: appearance follows wear, hash depends on fan and contacts, seller confrontation

Fan, contact and scratch levels are recomputed from health and a per-card
seed, so a card looks older as it wears. Fan and contacts scale hashrate.
Each inspection-tray detail now maps to exactly one dimension value. Quoting
dealers price the card as if its condition were fine; the player points out
fan, contacts, bracket, print or sticker in the tray to take the matching
discount or expose a fake (a wrong accusation costs reputation like the
vanilla confrontation). Unowned cards show a coarse grade and rated specs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 13:37:48 +08:00
1 parent 68265095a8
commit f93dbc6eec
9 files changed
+279 -76

No files matched your search

+33 -8
View File
@@ -137,8 +137,8 @@ internal static class Program
for (int i = 0; i < n; i++)
{
var real = CardLook.Generate(rng, 100, false);
var oldReal = CardLook.Generate(rng, 30, false);
var fake = CardLook.Generate(rng, 60, true);
var oldReal = CardLook.Generate(rng, 20 + (i % 40), false);
var fake = CardLook.Generate(rng, 20 + (i % 80), true);
if (real.HasFakeSign || oldReal.HasFakeSign) realSign++;
if (!fake.HasFakeSign) fakeNoSign++;
fakeSigns += (fake.Bracket == 2 ? 1 : 0) + (fake.Print != 0 ? 1 : 0) + (fake.Sticker == 1 ? 1 : 0);
@@ -340,27 +340,52 @@ internal static class Program
Check(Near(dr.Received, 900 * (1 - Tuning.ExchangeFee), 1e-6), "CRC 脱锚时按市价兑出");
});
Test("二手贩子:老实人没有假卡;骗子按标称价九折报价;假卡芯片至少便宜一半;买下后只值真实价值", () =>
Test("二手贩子:老实人不报价;中间商加价;骗子夹带假卡;指出问题按维度扣价、拆穿假货按真实芯片算", () =>
{
var rng = new Rng(7);
int fakes = 0, cards = 0;
for (int i = 0; i < 300; i++)
foreach (var c in UsedDealers.Cards(DealerKind.Honest, 40, rng))
Check(c.FakeOf == null && c.MarkupPct == 0, "老实人的卡是真卡且按真实价值");
Check(c.FakeOf == null && !c.Quoted, "老实人的卡是真卡且不报价");
for (int i = 0; i < 300; i++)
foreach (var c in UsedDealers.Cards(DealerKind.Scammer, 40, rng))
{
cards++;
if (c.FakeOf == null) continue;
fakes++;
var u = new Unit { Model = c.Model, FakeOf = c.FakeOf, Health = c.Health };
Check(Hardware.Specs[c.FakeOf].Price * 2 <= Hardware.Specs[c.Model].Price, "假卡的芯片至少便宜一半");
long shown = (long)Math.Round(CardValue.True(u) * (1 + c.MarkupPct / 100.0));
Check(Math.Abs(shown - CardValue.Of(c.Model, c.Health) * UsedDealers.ScamDiscount) <= CardValue.True(u) * 0.01 + 1, "柜台价约等于标称价九折");
Check(c.MarkupPct > 0, "假卡的柜台价高于真实价值");
}
Check(fakes > cards * 0.2 && fakes < cards * 0.5, $"骗子的假卡占两成多(入门卡便宜到没法冒充):{fakes}/{cards}");
Check(UsedDealers.Pool(5).All(m => Dealer.Basic.Contains(m)) && UsedDealers.Pool(30).Contains("RZX90"), "型号按天数放开");
// 报价:假 RZX 70(芯片 RZX 50)、健康 60、风扇缺角、金手指氧化
var u = new Unit { Model = "RZX70", FakeOf = "RZX50", Health = 60, Look = new CardLook { Fan = 2, Fingers = 2 } };
long label = CardValue.Of("RZX70", 60), real = CardValue.True(u);
Check(CardValue.Quote(u, 0, 0, false) == (long)Math.Round(label * 0.9), "没拆穿的假卡:标称型号的价打九折,外观问题还没指出就不扣");
Check(CardValue.Quote(u, 0, CardLook.DimFan, false) == (long)Math.Round(label * 0.88 * 0.9), "指出风扇缺角:扣 12%");
Check(CardValue.Quote(u, 0, CardLook.AllDims, true) == real, "外观问题都指出并拆穿假货:报价等于真实价值");
var honest = new Unit { Model = "RZX70", Health = 60, Look = new CardLook { Fan = 1 } };
Check(CardValue.Quote(honest, 8, 0, false) == (long)Math.Round(CardValue.Of("RZX70", 60) * 1.08), "中间商:加价 8%,外观问题没指出就不扣");
Check(CardValue.Quote(honest, 8, CardLook.AllDims, false) == (long)Math.Round(CardValue.Of("RZX70", 60) * 0.96 * 1.08), "指出积灰:扣 4%");
});
Test("外观随损耗重算:越旧外观越差;种子固定;算力受风扇与金手指影响", () =>
{
var rng = new Rng(5);
int worse = 0, total = 400;
for (int i = 0; i < total; i++)
{
var look = CardLook.Generate(rng, 100, false);
var a = look.Appearance(100); var b = look.Appearance(20);
Check(b.Fan >= a.Fan && b.Fingers >= a.Fingers && b.Bracket >= a.Bracket, "健康度下降时外观只会变差");
if (b.Fan + b.Fingers + b.Bracket > a.Fan + a.Fingers + a.Bracket) worse++;
Check(look.Appearance(60).Encode() == look.Appearance(60).Encode(), "同一健康度外观固定");
}
Check(worse > total * 0.9, $"健康度从 100 掉到 20,九成以上的卡外观变差:{worse}/{total}");
var fake = CardLook.Generate(rng, 80, true);
Check(fake.HasFakeSign && fake.Appearance(10).HasFakeSign, "假卡的破绽不随健康度变化");
var clean = new Unit { Model = "RZX70", Health = 100 };
var bad = new Unit { Model = "RZX70", Health = 100, Look = new CardLook { Fan = 2, Fingers = 2 } };
Check(Near(bad.Hash(ModuleBonus.None) / clean.Hash(ModuleBonus.None), 0.92 * 0.94, 1e-9), "风扇缺角 + 金手指氧化:算力 ×0.92 ×0.94");
});
Test("杰克逊博士:没有矿架必上一个;基础卡每次两张;进阶第 10 天起、旗舰第 30 天起;特供货第 20 天起必有旗舰", () =>