Skip to main content

Luck Pool

1. Summary

Goal: Поощрение активных игроков через систему вероятностного буста. Игроки, активно собирающие материалы для крафта, получают увеличенные шансы на нужные компоненты.

User Value: Активные игроки (50%+ прогресс крафта) получают × 3-13 увеличение шансов на материалы для целевых скинов. Чем дольше в пуле — тем выше буст.


2. Business Logic

Pool Mechanics

Условие: Прогресс крафта любого скина >= 50%

Базовый буст: × 3.0 к весам FRAGMENT/BLUEPRINT

Автоматически: Cron job проверяет каждые 12 часов

Как считается прогресс
progress = collectedMaterials / totalMaterials

Где totalMaterials — сумма всех материалов в рецепте крафта скина.

Boost Application

Формула применения буста
boostedWeight = originalWeight × baseMultiplier × seniorityMultiplier

Где baseMultiplier = 3.0, seniorityMultiplier = 1.2^(activePeriods - 1)

Что бустится:

  • FRAGMENT для скинов в пуле
  • BLUEPRINT для скинов в пуле
  • НЕ бустится: SCRAP, XP, другие награды

Где применяется:

  • Cases — CaseOpeningService.applyBoostToWeights()
  • Daily Spins — UserSpinService.applyBoostToWeights()
Пример применения
// Игрок в пуле: activePeriods = 3, скин "AWP Dragon Lore" >= 50%
// boostConfig.totalMultiplier = 3.0 × 1.44 = 4.32

// Награда: Fragment для AWP Dragon Lore
// originalWeight = 100
// boostedWeight = 100 × 4.32 = 432

// Шанс выпадения вырос в 4.32 раза!

Protection

ДействиеRate LimitAuthValidation
GET /entriesgeneralAdmin JWT
GET /statsgeneralAdmin JWT
GET /user/:userIdgeneralAdmin JWTuserId param
POST /processmutationsAdmin JWT
Детали реализации

См. Security Matrix для полного обзора защит.

Edge Cases

СитуацияПоведение
Игрок достиг 50%Автоматически добавляется в пул (cron 12ч)
Игрок скрафтил скинВыход из пула, сброс seniority, блокировка скинов
14 дней AFKВыход из пула, seniority сохраняется
Все скины заблокированыИгрок не может войти в пул до снятия блокировки
Конец месяцаblockedSkinIds очищаются
Конец периодаactivePeriods++ для активных игроков
Вход после крафтаТолько после canReenterAfter и при наличии незаблокированных скинов

3. ADR (Architectural Decisions)

Почему буст только для FRAGMENT/BLUEPRINT?

Проблема: Буст всех наград инфлирует экономику. Игрок получает больше Scrap/XP просто за нахождение в пуле.

Решение: Бустить только материалы для целевых скинов.

Альтернативы (отклонены):

  • Буст всех наград — инфляция, нарушает баланс
  • Буст только Blueprint — слишком узко, не мотивирует

Последствия: Игрок в пуле быстрее собирает конкретный скин, но не получает экономических преимуществ.

Почему Seniority сбрасывается при крафте?

Проблема: Игрок может накопить × 12.9 буст и никогда не крафтить, только собирая материалы.

Решение: Сброс activePeriods = 1 после каждого крафта.

Альтернативы (отклонены):

  • Не сбрасывать — приводит к бесконечному накоплению
  • Частичный сброс (÷2) — усложняет логику без выгоды

Последствия: Мотивация крафтить регулярно, а не копить буст. Защита от злоупотреблений.

Почему AFK сохраняет seniority?

Проблема: Игрок уезжает в отпуск на 3 недели, теряет весь стаж.

Решение: AFK удаляет из пула, но сохраняет activePeriods.

Альтернативы (отклонены):

  • Полный сброс — слишком жёстко, игроки уйдут
  • Уменьшение за каждую неделю AFK — сложная логика

Последствия: Игроки могут вернуться без потери прогресса. Retention выше.


4. Architecture

Integration Overview

Key Components

КомпонентПутьОписание
LuckPoolServicebackend/src/domains/luck-pool/services/luck-pool.service.tsCore business logic (535 lines)
LuckPoolRepositorybackend/src/domains/luck-pool/repositories/luck-pool.repository.tsDatabase operations
LuckPoolAdminControllerbackend/src/domains/luck-pool/controllers/luck-pool-admin.controller.tsAdmin HTTP handlers
Admin Routesbackend/src/domains/luck-pool/routes/admin-luck-pool.routes.tsRoute registration
Schemasbackend/src/domains/luck-pool/schemas/luck-pool.schemas.tsFastify JSON schemas
Typesbackend/src/domains/luck-pool/types/luck-pool.types.tsTypeScript interfaces
Constantsshared/src/constants/budgetControl.tsAll system constants
Константы системы
BUDGET_CONTROL = {
// Pool Configuration
MIN_PROGRESS_FOR_POOL: 0.5, // 50%
INACTIVE_DAYS_THRESHOLD: 14, // Days for AFK detection
POOL_CHECK_INTERVAL_HOURS: 12, // Cron job interval

// Boost
BASE_BOOST_MULTIPLIER: 3.0,

// Seniority
SENIORITY: {
MULTIPLIER_PER_PERIOD: 1.2,
MAX_ACTIVE_PERIODS: 9,
},

// Blocked Skins
BLOCKED_SKINS: {
CLEAR_INTERVAL_PERIODS: 3, // Clear after 3 periods (1 month)
},

// Activity Definition
ACTIVITY_ACTIONS: [
'CASE_OPENED',
'SPIN_USED',
'STREAK_BONUS_CLAIMED',
],
}

Service Methods

МетодОписаниеВызывается из
getUserPoolStatus()Статус участия и бустAdmin API, Frontend
getBoostConfig()Конфиг буста для RNGCaseOpeningService, UserSpinService
isUserInPool()Простая проверкаInternal
addToPool()Добавить в пулprocessPoolUpdates cron
handleCraft()Обработка крафтаCraftService
markUserInactive()AFK удалениеprocessPoolUpdates cron
updateActivity()Обновить активностьCaseOpeningService, UserSpinService
processPoolUpdates()Cron: новые, AFK, reentrySeasonLifecycleJob
handlePeriodChange()Cron: increment senioritySeasonLifecycleJob
clearExpiredBlockedSkins()Cron: очистка блокировокSeasonLifecycleJob

Admin Notification (TG Alert)

После выполнения processPoolUpdates() отправляется уведомление администратору:

СобытиеТипКогда отправляется
Обновление пулаPOOL_UPDATEПосле каждой обработки пула (каждые 12 часов)
Формат уведомления
📊 Обновление Luck Pool

Сезон: {seasonNumber}
Добавлено в пул: +{added}
Удалено (AFK): -{removedAFK}
Вернулись после крафта: +{reenteredAfterCraft}
Всего в пуле: {totalInPool}

5. Database Schema

Models

МодельОписаниеКлючевые поля
LuckPoolEntryУчастник пулаactivePeriods, boostMultiplier, isActive, blockedSkinIds

LuckPoolEntry Fields

ПолеТипОписание
idStringPrimary key (CUID)
userIdStringFK to User
seasonIdStringFK to Season
activePeriodsIntКоличество активных периодов (seniority)
boostMultiplierFloatТекущий множитель (BASE × seniority)
enteredAtDateTimeКогда впервые вошёл в пул
lastActiveAtDateTimeПоследняя активность
isActiveBooleanТекущий статус в пуле
lastCraftAtDateTime?Когда последний раз крафтил
canReenterAfterDateTime?Когда можно вернуться после крафта
blockedSkinIdsString[]Заблокированные скины (≥50% при крафте)
blockedUntilDateTime?Когда истекает блокировка

Relationships

Indexes

  • @@unique([userId, seasonId]) — один entry на user/season
  • @@index([seasonId, isActive]) — быстрый поиск активных
  • @@index([canReenterAfter]) — для reentry cron

6. API Endpoints

Admin Only

Luck Pool не имеет user-facing API. Все эндпоинты только для админ-панели.

МетодЭндпоинтОписание
GET/admin/luck-pool/entriesВсе участники пула
GET/admin/luck-pool/statsСтатистика пула
GET/admin/luck-pool/user/:userIdСтатус пользователя
POST/admin/luck-pool/processРучной запуск обработки

Response Examples

GET /admin/luck-pool/stats
{
"success": true,
"data": {
"total": 150,
"active": 120,
"avgSeniority": 3.4,
"avgBoost": 5.8
}
}
GET /admin/luck-pool/user/:userId
{
"success": true,
"data": {
"isInPool": true,
"entry": {
"id": "clxx...",
"userId": "user123",
"seasonId": "season456",
"activePeriods": 3,
"boostMultiplier": 4.32,
"enteredAt": "2024-01-15T10:00:00Z",
"lastActiveAt": "2024-01-20T15:30:00Z",
"isActive": true,
"blockedSkinIds": [],
"blockedUntil": null
},
"eligibleSkins": [
{
"skinId": "item123",
"skinName": "AWP Dragon Lore",
"progress": 0.65,
"isBlocked": false,
"totalMaterials": 100,
"collectedMaterials": 65
}
],
"boostMultiplier": 4.32,
"seniorityMultiplier": 1.44
}
}

  • Budget Control — общий контекст Budget Control System
  • Cases — использует Luck Pool для буста материалов
  • Daily Spins — использует Luck Pool для буста материалов
  • Craft — триггерит выход из пула
  • Seasons — контекст для периодов и пула
  • Inventory — прогресс крафта из инвентаря