276 lines
10 KiB
Markdown
276 lines
10 KiB
Markdown
|
|
# DATA_PIPELINE.md — Техническое решение каталога предметов
|
|||
|
|
|
|||
|
|
> **Проблема:** ~10 000 скинов CS:GO × 5 wear × 10+ лет = миллионы записей цен. Вручную невозможно.
|
|||
|
|
>
|
|||
|
|
> **Решение:** 3-слойная архитектура. Якоря вручную + процедурка + опц. скрейпинг.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## I. АРХИТЕКТУРА — 3 СЛОЯ
|
|||
|
|
|
|||
|
|
### Слой 1: ЯКОРЯ (вручную, 100-150 шт)
|
|||
|
|
**Источник:** `canon/HISTORICAL_STEAM_ITEMS.md` от research-агента (4800 слов).
|
|||
|
|
|
|||
|
|
**Что покрыто:**
|
|||
|
|
- 30 iconic скинов с реальными ценами по wear/year (Dragon Lore, Howl, Fire Serpent, etc.)
|
|||
|
|
- 50 кейсов с датами выхода и ценами по кварталам
|
|||
|
|
- 13 Australium weapons с ценами 2020-2024
|
|||
|
|
- 15+ Unusual TF2 effects с типовыми ценами
|
|||
|
|
- PUBG sezony S7-S25 с pass-ценами
|
|||
|
|
- Все CS:GO Operations с pass-ценами и дроп-листами
|
|||
|
|
|
|||
|
|
**Используется как:** **ground truth** для калибровки процедурного слоя.
|
|||
|
|
|
|||
|
|
### Слой 2: ПРОЦЕДУРНЫЙ ГЕНЕРАТОР (формула, генерирует тысячи)
|
|||
|
|
**Источник:** `canon/STEAM_ITEMS.md` секция VII (формула ценообразования) + `canon/COLLECTIONS_TIMELINE.md` (статус коллекций по датам).
|
|||
|
|
|
|||
|
|
**Алгоритм:**
|
|||
|
|
```
|
|||
|
|
для каждого скина в каталоге:
|
|||
|
|
base = base_price[rarity_tier] # из COLLECTIONS_TIMELINE
|
|||
|
|
status = collection_status_on_date # active/legacy/retired/contraband
|
|||
|
|
|
|||
|
|
для каждого wear FN/MW/FT/WW/BS:
|
|||
|
|
price = base
|
|||
|
|
× wear_multiplier[wear] # 0.3-4.0 по STEAM_ITEMS.md
|
|||
|
|
× status_multiplier[status] # 1.0-30.0 (retired = expensive)
|
|||
|
|
× time_curve(date, release) # 0.2-5.0
|
|||
|
|
× stattrak_or_souvenir # 1.0-3.0
|
|||
|
|
× pattern_jitter() # 0.85-1.15
|
|||
|
|
|
|||
|
|
если skin in ANCHORS (якорь из слоя 1):
|
|||
|
|
скорректировать формулу чтобы совпадало с якорем ±10%
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Что генерируется:** 3000-5000 уникальных скинов × 5 wear = 15-25 тыс записей.
|
|||
|
|
|
|||
|
|
**Хранится как:** `assets/data/skins.json` — генерируется один раз при build-е.
|
|||
|
|
|
|||
|
|
### Слой 3: РЕАЛТАЙМ-СКРЕЙПИНГ (опционально, для энтузиастов)
|
|||
|
|
**Источник:** Steam Market public API.
|
|||
|
|
|
|||
|
|
**Endpoint:** `https://steamcommunity.com/market/priceoverview/?currency=5&appid=730&market_hash_name=<skin>` (currency=5 = RUB)
|
|||
|
|
|
|||
|
|
**Что даёт:** реальная цена на сегодня для любого скина.
|
|||
|
|
|
|||
|
|
**Использование в игре:**
|
|||
|
|
- В пред-альфа: НЕ используется (offline-only)
|
|||
|
|
- В будущей версии: опциональная "live-pricing" механика для игроков с интернетом
|
|||
|
|
|
|||
|
|
**Скорость:** Steam ограничивает ~20 req/sec → 30к скинов = 25 минут на полный апдейт.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## II. JSON-СТРУКТУРА
|
|||
|
|
|
|||
|
|
### Файл `assets/data/skins.json`
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": "1.0.0",
|
|||
|
|
"generated_at": "2026-05-24T15:00:00Z",
|
|||
|
|
"base_date": "2020-08-21",
|
|||
|
|
"skins": [
|
|||
|
|
{
|
|||
|
|
"id": "awp_dragon_lore",
|
|||
|
|
"weapon": "AWP",
|
|||
|
|
"name": "Dragon Lore",
|
|||
|
|
"collection": "The Cobblestone Collection",
|
|||
|
|
"rarity": "covert",
|
|||
|
|
"category": "rifle_sniper",
|
|||
|
|
"released": "2014-07-01",
|
|||
|
|
"collection_status_timeline": [
|
|||
|
|
{"from": "2014-07-01", "status": "active"},
|
|||
|
|
{"from": "2018-04-01", "status": "rare"},
|
|||
|
|
{"from": "2023-09-27", "status": "legacy_cs2"}
|
|||
|
|
],
|
|||
|
|
"base_price_rub_2020": 90000,
|
|||
|
|
"wear_multipliers": {
|
|||
|
|
"FN": 2.5,
|
|||
|
|
"MW": 1.7,
|
|||
|
|
"FT": 1.0,
|
|||
|
|
"WW": 0.7,
|
|||
|
|
"BS": 0.5
|
|||
|
|
},
|
|||
|
|
"stattrak_available": true,
|
|||
|
|
"souvenir_available": true,
|
|||
|
|
"souvenir_multiplier": 3.0,
|
|||
|
|
"low_float_supported": true,
|
|||
|
|
"is_anchor": true
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Файл `assets/data/cases.json`
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"cases": [
|
|||
|
|
{
|
|||
|
|
"id": "fracture_case",
|
|||
|
|
"name": "Fracture Case",
|
|||
|
|
"released": "2020-08-06",
|
|||
|
|
"collection_status_timeline": [
|
|||
|
|
{"from": "2020-08-06", "status": "active"},
|
|||
|
|
{"from": "2023-09-27", "status": "legacy_cs2"}
|
|||
|
|
],
|
|||
|
|
"key_cost_rub_2020": 190,
|
|||
|
|
"drop_weights": {
|
|||
|
|
"mil_spec": 0.7992,
|
|||
|
|
"restricted": 0.1598,
|
|||
|
|
"classified": 0.0320,
|
|||
|
|
"covert": 0.0064,
|
|||
|
|
"knife": 0.0026
|
|||
|
|
},
|
|||
|
|
"knife_pool": ["bayonet", "karambit", "m9_bayonet", "..."]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Файл `assets/data/collections_timeline.json`
|
|||
|
|
Структурированный экспорт `canon/COLLECTIONS_TIMELINE.md` в JSON.
|
|||
|
|
|
|||
|
|
### Файл `assets/data/cs2_transition.json`
|
|||
|
|
События 27.09.2023: смена UI, market shock, panel updates.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## III. ГЕНЕРАТОР В GODOT
|
|||
|
|
|
|||
|
|
### Скрипт `scripts/data/skins_generator.gd`
|
|||
|
|
```gdscript
|
|||
|
|
extends Node
|
|||
|
|
|
|||
|
|
const ANCHORS_JSON = "res://assets/data/anchors.json" # из HISTORICAL_STEAM_ITEMS.md
|
|||
|
|
const COLLECTIONS_JSON = "res://assets/data/collections_timeline.json"
|
|||
|
|
|
|||
|
|
func generate_full_catalog(seed_value: int) -> Array:
|
|||
|
|
var rng = RandomNumberGenerator.new()
|
|||
|
|
rng.seed = seed_value
|
|||
|
|
|
|||
|
|
var anchors = _load_anchors()
|
|||
|
|
var collections = _load_collections()
|
|||
|
|
var catalog = []
|
|||
|
|
|
|||
|
|
for collection in collections:
|
|||
|
|
for skin_template in _generate_skins_for_collection(collection, rng):
|
|||
|
|
for wear in ["FN", "MW", "FT", "WW", "BS"]:
|
|||
|
|
var entry = _build_entry(skin_template, wear, collection, rng)
|
|||
|
|
catalog.append(entry)
|
|||
|
|
|
|||
|
|
return catalog
|
|||
|
|
|
|||
|
|
|
|||
|
|
func get_price(skin_id: String, wear: String, on_date: String) -> int:
|
|||
|
|
# Расчёт цены любого скина на любую дату
|
|||
|
|
var skin = _find_skin(skin_id)
|
|||
|
|
var collection_status = _status_on_date(skin.collection, on_date)
|
|||
|
|
var time_curve = _time_curve(skin.released, on_date)
|
|||
|
|
|
|||
|
|
return skin.base_price \
|
|||
|
|
* skin.wear_multipliers[wear] \
|
|||
|
|
* _status_multiplier(collection_status) \
|
|||
|
|
* time_curve \
|
|||
|
|
* _daily_jitter(on_date, skin_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Скрипт `scripts/data/market_simulator.gd`
|
|||
|
|
- Симулирует ежедневные флуктуации ±5-15%
|
|||
|
|
- Среда -10% (канон-механика)
|
|||
|
|
- 5.12.2023 крах -10-20%
|
|||
|
|
- 24.02.2022 курс-скачок +30-50% в RUB
|
|||
|
|
- CS2 transition 27.09.2023 спайк ±30-50%
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## IV. УПРОЩЁННАЯ ВЕРСИЯ ДЛЯ MVP (пролога)
|
|||
|
|
|
|||
|
|
### Что нужно в Акте 1-13 пролога
|
|||
|
|
- **Australium Wrench** (1 скин) — захардкоден в коде
|
|||
|
|
- **Кейс кесе го Prime** (после покупки игры) — выпадает что-то от Fracture Case (свежий на 21.08.2020). 5-7 возможных скинов рандомно.
|
|||
|
|
- **Кейс Пабаджи** — Survivor Crate, 5-7 косметик из реала
|
|||
|
|
|
|||
|
|
**ИТОГО для пролога:** ~15-20 скинов в JSON, не тысячи. Полный генератор — для основной игры.
|
|||
|
|
|
|||
|
|
### Файл `assets/data/skins_prolog.json` (минимум)
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"australium_wrench": { ... },
|
|||
|
|
"fracture_case_skins": [
|
|||
|
|
"ak47_legion_of_anubis",
|
|||
|
|
"m4a4_tooth_fairy",
|
|||
|
|
"ump45_gold_bismuth",
|
|||
|
|
"..."
|
|||
|
|
],
|
|||
|
|
"pubg_survivor_crate_items": [
|
|||
|
|
"trench_coat",
|
|||
|
|
"leather_jacket",
|
|||
|
|
"..."
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## V. PIPELINE BUILD
|
|||
|
|
|
|||
|
|
### CLI команда (Forgejo Actions может запускать)
|
|||
|
|
```bash
|
|||
|
|
# Из корня проекта
|
|||
|
|
godot --headless --script scripts/data/build_catalog.gd --seed 42
|
|||
|
|
# → генерирует assets/data/skins.json (15k записей)
|
|||
|
|
# → проверяет соответствие якорям ±10%
|
|||
|
|
# → выходит с кодом 0 если ок, 1 если рассогласование
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### В `.forgejo/workflows/build.yml` (когда дойдём)
|
|||
|
|
Добавить step:
|
|||
|
|
```yaml
|
|||
|
|
- name: Build catalog
|
|||
|
|
run: godot --headless --script scripts/data/build_catalog.gd --seed 42
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## VI. КОГДА ЧТО ДЕЛАТЬ
|
|||
|
|
|
|||
|
|
| Этап | Что |
|
|||
|
|
|---|---|
|
|||
|
|
| **Сейчас (Акт 1 пролога)** | Не нужен полный каталог. 15-20 скинов руками в `skins_prolog.json`. |
|
|||
|
|
| **Акт 12-13 пролога (3 фарм-акка)** | Добавить 10-20 базовых скинов кесе го/Пабаджи |
|
|||
|
|
| **Эпоха 1 (9 класс)** | Полный каталог: ~3000 скинов через генератор. Реализация `skins_generator.gd`. |
|
|||
|
|
| **Эпоха 3 (10 класс, окт 2021)** | Добавить Riptide коллекции |
|
|||
|
|
| **Эпилог (CS2 transition)** | State-machine "era": до/после 27.09.2023 |
|
|||
|
|
| **Финальный релиз** | Опц. реалтайм-скрейпинг для энтузиастов |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## VII. РАЗМЕР DATASET
|
|||
|
|
|
|||
|
|
| Слой | Записей | Размер JSON |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Anchors (вручную) | 150 | ~50 КБ |
|
|||
|
|
| Процедурный CS:GO/CS2 | 15 000 | ~5-8 МБ |
|
|||
|
|
| TF2 (Mannco + Australium + Unusual) | 8 000 | ~3-5 МБ |
|
|||
|
|
| PUBG | 2 000 | ~1 МБ |
|
|||
|
|
| **ИТОГО** | **25 000** | **~10-15 МБ** |
|
|||
|
|
|
|||
|
|
**Размер билда:** +15МБ — приемлемо (Godot project = 50 МБ базы, итого ~65МБ — без проблем для Steam $5 игры).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## VIII. ТРЕНИНГ-СЛОЙ (опционально, для будущего)
|
|||
|
|
|
|||
|
|
ML-модель которая обучается на anchors + Steam Market API данных за месяц → может предсказывать цены любого скина с точностью ±10-15%. Запускается в фоне игры, обновляет dataset.
|
|||
|
|
|
|||
|
|
**Не нужно для пролога.** Опция для долгосрочного развития.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## TBD
|
|||
|
|
|
|||
|
|
- Конкретный JSON-формат якорей (выкатим при первой генерации)
|
|||
|
|
- Карта rarity → base_price (5-7 уровней)
|
|||
|
|
- Какие именно 30 iconic скинов кодируем как якоря (взять из HISTORICAL_STEAM_ITEMS.md)
|
|||
|
|
- Логика state-machine "CS-era" (до/после 27.09.2023)
|