🎯 Фаза 1 — аноним vs бот + запись в аналитику
Цель. Незарегистрированный гость открывает play.jc.id.lv, играет полную партию с одним из наших ботов целиком в браузере, и партия попадает в dicechess-analytics как новый источник source='playsite' — с корректными идентичностями гостя и бота, детерминированным id и white-POV результатом, без риска для существующих 140k партий.
Игровой цикл уже реализован клиентски в lab (см. 02 Архитектура — авторитет и стек). Реальная работа фазы 1 — четыре вещи: снять auth-гейт, написать маппер, поднять шлюз, запинить версию движка.
Реализовано и проверено вживую (2026-06-23)
Сайт: dicechess-play.pages.dev (Cloudflare Pages, авто-деплой). Шлюз:
dicechess-ingest-gatewayна Koyeb. Реальные партии пишутся:source='playsite',guest:<uuidv7>(per-browser) +bot:<algorithm>, UUIDv5-id, white-POV результат, терминация из движка. Гоча: при cold-start Koyeb первая партия летит ~12с (ингест асинхронный — ок). Отклонённые партии (400/422) карантинятся в IndexedDB (не ретраятся). Известный edge-case: один POST дал 422 (реплей отверг партию) — повод довести golden-corpus.
Идентичность (как пятый писатель)
Полная схема — в 08 Идентичность, источники и дедупликация. Для playsite:
| Сущность | external_id | player_type | source |
|---|---|---|---|
| гость (человек) | guest:<uuidv7> — per-browser, в localStorage | guest | playsite |
| бот | bot:<algorithm> (напр. bot:monte-carlo) | bot | playsite |
source='playsite'— новое значение.games.sourceэтоVARCHAR(20)без enum/whitelist → backend аналитики не меняется вообще.bot:<algorithm>делим с extension: это те же наши движковые алгоритмы. Происхождение партии различается полемgames.source, потерь нет; агрегаты по игроку-боту фильтровать поsource.player_typeлипкий (ставится только при первом insert) → маппер хардкодитguest/bot, чтобы навсегда не промахнуться.- id партии:
UUIDv5('playsite/game/<clientGameUuid>')— детерминированно, ретраи идемпотентны (first-writer-wins, 200 на дубль), как у beturanga.
guestу playsite ≠guestу syncВ sync
guestозначает «эфемерный аккаунт dicechess.com, не краулим». У playsiteguest:<uuidv7>— наш собственный анонимный игрок, по которому мы хотим копить статистику (per-browser). Разные смыслы, общийplayer_type; различаются формойexternal_id(guest:+ UUID против имениGuest…).
Маппер: история lab → GameIngestWire
Чистый маппер (~150 строк), строит payload напрямую из per-turn истории lab (НЕ через normalizeStateMap). Поля (snake_case):
turns[i]→{turn_number, active_color (w/b), dice[] (1..6, декод из 7-го поля DFEN), moves[] (UCI; [] для паса)};initial_fen= стандартный стартовый FEN;result— white-POV (1 / -1 / 0), победитель известен точно;termination— из терминального состояния движка (countKings → king_captured), не из догадки UI;white_player/black_player— гость vs бот по схеме выше;mode='classic';initial_stake_amountи денежные дельты = NULL (свободные партии не должны пачкать stake-аналитику).
Путь ингеста
flowchart LR SPA["SPA · запись в IndexedDB<br/>(sync_status pending)"] -->|маппер → GameIngestWire| G["Шлюз · Koyeb<br/>локальный реплей движком"] G -->|"Bearer · POST /api/games"| S["sync.jc.id.lv"] S --> A[("analytics")] G -.->|реджект| Q["карантин в IndexedDB<br/>(ручной разбор)"]
См. топологию и обоснование шлюза в 02 Архитектура — авторитет и стек и ADR-0005 Шлюз ингеста на Koyeb.
Гарантия валидности (0 × 422, 0 загрязнения)
- Пин движка ≥ 1.4.3 = версия backend — главный источник тихих 422.
- Boot-time assert «client gateway backend version».
- Два движковых гейта: локальный реплей на шлюзе + реплей backend.
- Golden-corpus в CI: пасы (
moves=[]), превращения, en-passant в середине хода, king-capture в середине последовательности — должны реиграться чисто и локально, и на staging. - Идемпотентные UUIDv5 — ретраи безопасны (200 на дубль).
- Исход из движка, не из UI — backend доверяет
result/terminationбез перекрёстной проверки.
См. полный разбор причин 422 в 07 Контракт ingest и валидация движком.
Скоуп фазы 1 (чеклист)
- Форк
dicechess-playизdicechess-lab/frontend-pwa; снятьApp.svelteauth-гейт → рендер для null/guest. - Пин
@rabestro/dicechess-engine≥ 1.4.3; движок в Web Worker. - Хардкод 5 играбельных ботов (
random/checkmate-aware/greedy/aggressive/monte-carlo); НЕ доверятьgetAvailableBots(). - Гость:
guest:<uuidv7>per-browser вlocalStorage+ restore-code UX. - Отключить ставки/кошелёк/x2/удвоение для гостей; classic-only, stake = NULL.
- Маппер history →
GameIngestWire(+ исход из терминала движка). - Токен-шлюз на Koyeb с локальным реплеем; форвард на
sync.jc.id.lv. - IndexedDB-first outbox (retry/quarantine) + golden-corpus.
- Деплой SPA (
adapter-static) заplay.jc.id.lv.
Фишер с рокировкой запрещён
Движок хардкодит поля рокировки (e1/h1/a1) и не поддерживает Chess960 → партия Фишера с рокировкой отлетит 422 на реплее. Режим Фишера выключаем на всех фазах.