Недельный PvE-предел синих кристаллов: 6 за 7 реальных суток, сверх -- золото (регламент валют 05.11, ответ 06.11)

- CrystalQuota (без Unity) + 6 тестов из раздела 12
- Задания, события, бои: в пределах квоты -- кристаллы, сверх -- 1000 золота за кристалл (ТЕСТ)
- Ждущие места кристаллы квоту второй раз не тратят; письмо «ждут места» -- вне квоты
- Окно Ратуши: «Новых кристаллов за 7 дней: N / 6»; DEV: «+1 PvE-кристалл», «Сбросить квоту»

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-06 16:24:16 +01:00
co-authored by Claude Opus 5.5
parent e05cf87173
commit 277afdf84e
9 changed files with 267 additions and 12 deletions
@@ -514,3 +514,73 @@ public class CrystalStorageTests
Assert.AreEqual(0, r.Left("q"), "больше ничего не ждёт -- выдан ровно один раз");
}
}
// Недельный PvE-предел синих кристаллов (регламент валют 05.11, разд. 5.2, 10, 12; ответ 06.11, п. 2)
public class CrystalQuotaTests
{
const long T0 = 1_800_000_000_000L;
const long Day = CrystalQuota.Day;
[Test]
public void Quota_5of6_Reward2_OneCrystal_OneCompensated()
{
var q = new CrystalQuota(6, 7 * Day);
Assert.AreEqual(5, q.Take("a", 5, T0));
Assert.AreEqual(1, q.Take("b", 2, T0 + 1000), "5/6 + 2 -> 1 кристалл (второй -- компенсацией)");
Assert.AreEqual(0, q.Left(T0 + 1000));
}
[Test]
public void Quota_6of6_Purchase_Blocked()
{
var q = new CrystalQuota(6, 7 * Day);
q.Take("a", 6, T0);
Assert.IsFalse(q.CanTakeAll(1, T0 + 1), "6/6 -- покупка у торговца блокируется (золото не списывается)");
Assert.AreEqual(0, q.Take("b", 1, T0 + 1));
}
[Test]
public void Quota_SameOperationTwice_CountedOnce()
{
var q = new CrystalQuota(6, 7 * Day);
Assert.AreEqual(2, q.Take("event_5", 2, T0));
Assert.AreEqual(0, q.Take("event_5", 2, T0 + 5), "повторное «Забрать» -- квота не берётся второй раз");
Assert.AreEqual(2, q.Used(T0 + 5));
}
[Test]
public void Quota_RollingWindow_FreesAfter7Days_NoCarryOver()
{
var q = new CrystalQuota(6, 7 * Day);
q.Take("a", 6, T0);
Assert.AreEqual(0, q.Left(T0 + 7 * Day - 1), "ещё не прошло 7 суток");
Assert.AreEqual(T0 + 7 * Day, q.NextFreeMs(T0 + Day));
Assert.AreEqual(6, q.Left(T0 + 7 * Day), "через 7 суток квота снова 6, а не 6 + перенос");
Assert.AreEqual(6, q.Take("b", 9, T0 + 7 * Day), "сверх 6 -- компенсацией, на следующую неделю не переносится");
}
[Test]
public void Quota_SurvivesSaveAndLoad()
{
var q = new CrystalQuota(6, 7 * Day);
q.Take("a", 4, T0);
var copy = new CrystalQuota(6, 7 * Day);
copy.Load(q.ToLines());
Assert.AreEqual(2, copy.Left(T0 + Day), "после перезапуска -- те же 4 из 6");
Assert.IsTrue(copy.Taken("a"));
}
[Test]
public void Quota_SeparateFromStorage_WaitingNotCountedTwice()
{
// Квота есть, а места нет: выданное по квоте ждёт места и второй раз квоту не тратит
var q = new CrystalQuota(6, 7 * Day);
var r = new CrystalRewards();
int byQuota = q.Take("q1", 2, T0);
Assert.AreEqual(2, byQuota);
Assert.AreEqual(0, r.Claim("q1", byQuota, 50, 50), "хранилище 50/50 -- ничего не начислено");
Assert.AreEqual(2, r.Left("q1"), "2 ждут места");
Assert.AreEqual(2, r.Claim("q1", 0, 40, 50), "место появилось -- выданы ждущие");
Assert.AreEqual(2, q.Used(T0 + 10), "квота потрачена один раз: 2, а не 4");
}
}