Подробный список изменений: - Перенесены эффекты частиц из C#-кода в готовые префабы-ассеты (Vfx Prefabs) в папке Assets/Resources/Vfx/ с автоматической генерацией материалов в Editor. - Исправлены ошибки duration на играющих системах частиц и розовые текстуры в GameVfxService.cs. - Исправлена навигация и фокус кнопок на оверлеях ContinueGameOverlay и PrizeResultOverlay с автоматическим подсвечиванием кнопок лимонно-зеленым цветом при фокусе и проигрыванием звука. - Добавлена проверка настроек в GameplayHudController.cs для скрытия ContinueGameOverlay, если продолжение игры отключено. - Расширен сбор статистики: создана структура GameplaySessionRecord, добавлена история до 50 раундов в PrizeSessionStateService и JsonPrizeSessionStateRepository, выведен расчет средних показателей и список 5 последних раундов в сервисной статистике. - Повышена надежность COM-транспорта: добавлен sleep (80мс) в Dispose для graceful shutdown потоков, реализован цикл переподключения до 10 попыток при обрыве связи. - Интегрированы настройки громкости с микшером: изменен шаг изменения громкости на 5% (0.05) для удобства, добавлен статус-чек и логи в ApplyServiceVolumes, расширены юнит-тесты на GetFloat параметры. - Созданы новые архитектурные гайды по аппаратному COM-транспорту и призовой экономике автомата в папке GUIDES/architecture/. - Оптимизирован и обновлен RAG-индекс проекта.
260 lines
11 KiB
C#
260 lines
11 KiB
C#
using DanroJump.Prizes;
|
|
using NUnit.Framework;
|
|
|
|
namespace DanroJump.Tests.EditMode
|
|
{
|
|
public sealed class PrizeSessionStateServiceTests
|
|
{
|
|
[Test]
|
|
public void TryBeginSession_CreatesActivePendingRecord()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
var result = new PrizeFlowResult(PrizeRewardKind.QRCode, 120, 4, 55f, "run-1");
|
|
|
|
var started = service.TryBeginSession(result, out var record);
|
|
|
|
Assert.That(started, Is.True);
|
|
Assert.That(record, Is.Not.Null);
|
|
Assert.That(record.SessionId, Is.EqualTo("run-1"));
|
|
Assert.That(record.Status, Is.EqualTo(PrizeSessionStatus.Pending));
|
|
Assert.That(service.ActiveSession, Is.SameAs(record));
|
|
Assert.That(service.History.Count, Is.EqualTo(1));
|
|
Assert.That(service.StartedCount, Is.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public void TryBeginSession_BlocksDuplicateSessionId()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
var result = new PrizeFlowResult(PrizeRewardKind.PrizeStand, 200, 2, 80f, "same-run");
|
|
|
|
Assert.That(service.TryBeginSession(result, out _), Is.True);
|
|
Assert.That(service.TryBeginSession(result, out var duplicate), Is.False);
|
|
|
|
Assert.That(duplicate.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
|
|
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
|
|
Assert.That(service.StartedCount, Is.EqualTo(1));
|
|
Assert.That(service.History.Count, Is.EqualTo(2));
|
|
}
|
|
|
|
[Test]
|
|
public void TryBeginSession_BlocksNewPrizeWhileAnotherSessionIsActive()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.QRCode, 120, 0, 40f, "active-run"), out var active);
|
|
|
|
var started = service.TryBeginSession(
|
|
new PrizeFlowResult(PrizeRewardKind.Big, 300, 1, 100f, "next-run"),
|
|
out var blocked);
|
|
|
|
Assert.That(started, Is.False);
|
|
Assert.That(service.ActiveSession, Is.SameAs(active));
|
|
Assert.That(blocked.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
|
|
Assert.That(blocked.Note, Does.Contain("ещё активна"));
|
|
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public void CompleteActiveSession_ClearsActiveSessionAndUpdatesStatistics()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.Big, 300, 0, 120f, "run-2"), out _);
|
|
service.MarkActiveSessionShown();
|
|
|
|
var completed = service.CompleteActiveSession("closed");
|
|
|
|
Assert.That(completed, Is.True);
|
|
Assert.That(service.ActiveSession, Is.Null);
|
|
Assert.That(service.CompletedCount, Is.EqualTo(1));
|
|
Assert.That(service.History[0].Status, Is.EqualTo(PrizeSessionStatus.Completed));
|
|
Assert.That(service.History[0].Note, Is.EqualTo("closed"));
|
|
}
|
|
|
|
[Test]
|
|
public void TryBeginSession_BlocksSameSessionAfterCompletion()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
var result = new PrizeFlowResult(PrizeRewardKind.Hopper, 400, 0, 130f, "completed-run");
|
|
service.TryBeginSession(result, out _);
|
|
service.CompleteActiveSession("done");
|
|
|
|
var restarted = service.TryBeginSession(result, out var blocked);
|
|
|
|
Assert.That(restarted, Is.False);
|
|
Assert.That(blocked.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
|
|
Assert.That(service.ActiveSession, Is.Null);
|
|
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public void FailActiveSession_ClearsActiveSessionAndUpdatesStatistics()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.QRCode, 150, 1, 60f, "run-3"), out _);
|
|
|
|
var failed = service.FailActiveSession("no phone");
|
|
|
|
Assert.That(failed, Is.True);
|
|
Assert.That(service.ActiveSession, Is.Null);
|
|
Assert.That(service.FailedCount, Is.EqualTo(1));
|
|
Assert.That(service.History[0].Status, Is.EqualTo(PrizeSessionStatus.Failed));
|
|
Assert.That(service.History[0].Note, Is.EqualTo("no phone"));
|
|
}
|
|
|
|
[Test]
|
|
public void BuildSummary_IncludesCountersAndLastRecord()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.Small, 90, 0, 20f, "run-4"), out _);
|
|
service.MarkActiveSessionShown();
|
|
|
|
var summary = service.BuildSummary();
|
|
|
|
Assert.That(summary, Does.Contain("начато 1"));
|
|
Assert.That(summary, Does.Contain("Активная выдача"));
|
|
Assert.That(summary, Does.Contain("малый приз"));
|
|
Assert.That(summary, Does.Contain("показана"));
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_RestoresCompletedSessionAndStillBlocksDuplicateAfterRestart()
|
|
{
|
|
var repository = new StubPrizeSessionStateRepository(new PrizeSessionStateSnapshot(
|
|
new[]
|
|
{
|
|
new PrizeSessionRecord(
|
|
"restored-run",
|
|
new PrizeFlowResult(PrizeRewardKind.Hopper, 400, 0, 120f, "restored-run"),
|
|
PrizeSessionStatus.Completed,
|
|
Utc(10),
|
|
Utc(11),
|
|
"done")
|
|
},
|
|
new[] { "restored-run" },
|
|
activeSessionId: string.Empty,
|
|
startedCount: 1,
|
|
completedCount: 1,
|
|
failedCount: 0,
|
|
duplicateBlockedCount: 0,
|
|
fallbackSessionCounter: 0));
|
|
|
|
var service = new PrizeSessionStateService(repository);
|
|
var restarted = service.TryBeginSession(
|
|
new PrizeFlowResult(PrizeRewardKind.Hopper, 400, 0, 120f, "restored-run"),
|
|
out var blocked);
|
|
|
|
Assert.That(restarted, Is.False);
|
|
Assert.That(blocked, Is.Not.Null);
|
|
Assert.That(blocked.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
|
|
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
|
|
Assert.That(service.History.Count, Is.EqualTo(2));
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_RecoversUnfinishedActiveSessionAsFailedAndPersistsUpdatedSnapshot()
|
|
{
|
|
var repository = new StubPrizeSessionStateRepository(new PrizeSessionStateSnapshot(
|
|
new[]
|
|
{
|
|
new PrizeSessionRecord(
|
|
"crash-run",
|
|
new PrizeFlowResult(PrizeRewardKind.QRCode, 150, 1, 60f, "crash-run"),
|
|
PrizeSessionStatus.Shown,
|
|
Utc(20),
|
|
Utc(21),
|
|
"qr opened")
|
|
},
|
|
new[] { "crash-run" },
|
|
activeSessionId: "crash-run",
|
|
startedCount: 1,
|
|
completedCount: 0,
|
|
failedCount: 0,
|
|
duplicateBlockedCount: 0,
|
|
fallbackSessionCounter: 0));
|
|
|
|
var service = new PrizeSessionStateService(repository);
|
|
|
|
Assert.That(service.ActiveSession, Is.Null);
|
|
Assert.That(service.FailedCount, Is.EqualTo(1));
|
|
Assert.That(service.History.Count, Is.EqualTo(1));
|
|
Assert.That(service.History[0].Status, Is.EqualTo(PrizeSessionStatus.Failed));
|
|
Assert.That(service.History[0].Note, Does.Contain("восстановлена после перезапуска"));
|
|
Assert.That(repository.SaveCalls, Is.EqualTo(1));
|
|
Assert.That(repository.LastSavedSnapshot, Is.Not.Null);
|
|
Assert.That(repository.LastSavedSnapshot.ActiveSessionId, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void StateChanges_PersistSnapshotOnBeginAndCompletion()
|
|
{
|
|
var repository = new StubPrizeSessionStateRepository();
|
|
var service = new PrizeSessionStateService(repository);
|
|
|
|
Assert.That(service.TryBeginSession(
|
|
new PrizeFlowResult(PrizeRewardKind.Big, 320, 0, 90f, "persist-run"),
|
|
out _), Is.True);
|
|
Assert.That(service.CompleteActiveSession("done"), Is.True);
|
|
|
|
Assert.That(repository.SaveCalls, Is.GreaterThanOrEqualTo(2));
|
|
Assert.That(repository.LastSavedSnapshot, Is.Not.Null);
|
|
Assert.That(repository.LastSavedSnapshot.CompletedCount, Is.EqualTo(1));
|
|
Assert.That(repository.LastSavedSnapshot.ActiveSessionId, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void StatisticsCounters_IncrementCorrectly()
|
|
{
|
|
var service = new PrizeSessionStateService();
|
|
service.RecordGameplaySession("test-session", 100, 5, 25.5f, 1, PrizeRewardKind.None);
|
|
Assert.That(service.TotalGamesPlayed, Is.EqualTo(1));
|
|
Assert.That(service.TotalScore, Is.EqualTo(100));
|
|
Assert.That(service.TotalCoins, Is.EqualTo(5));
|
|
|
|
service.RecordQrShow();
|
|
Assert.That(service.QrShows, Is.EqualTo(1));
|
|
|
|
service.RecordComError();
|
|
Assert.That(service.ComErrors, Is.EqualTo(1));
|
|
|
|
service.RecordPhysicalDispense(true);
|
|
Assert.That(service.PhysicalDispenses, Is.EqualTo(1));
|
|
Assert.That(service.PhysicalDispenseFailures, Is.EqualTo(0));
|
|
|
|
service.RecordPhysicalDispense(false);
|
|
Assert.That(service.PhysicalDispenses, Is.EqualTo(1));
|
|
Assert.That(service.PhysicalDispenseFailures, Is.EqualTo(1));
|
|
}
|
|
|
|
private static System.DateTime Utc(int minutes)
|
|
{
|
|
return new System.DateTime(2026, 6, 16, 12, minutes, 0, System.DateTimeKind.Utc);
|
|
}
|
|
|
|
private sealed class StubPrizeSessionStateRepository : IPrizeSessionStateRepository
|
|
{
|
|
private readonly PrizeSessionStateSnapshot snapshot;
|
|
|
|
public StubPrizeSessionStateRepository(PrizeSessionStateSnapshot snapshot = null)
|
|
{
|
|
this.snapshot = snapshot;
|
|
}
|
|
|
|
public int SaveCalls { get; private set; }
|
|
public PrizeSessionStateSnapshot LastSavedSnapshot { get; private set; }
|
|
|
|
public bool TryLoad(out PrizeSessionStateSnapshot loadedSnapshot)
|
|
{
|
|
loadedSnapshot = snapshot;
|
|
return loadedSnapshot != null;
|
|
}
|
|
|
|
public bool Save(PrizeSessionStateSnapshot newSnapshot)
|
|
{
|
|
SaveCalls++;
|
|
LastSavedSnapshot = newSnapshot;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|