# План фазы 4: этап 4 — ИИ ур. 4–6, MCTS + Web Worker (packages/ai) Обновлено: 2026-08-05 | Статус: реализовано (гейты зелёные, 118 тестов) ## Критические долги (реестр — в начале, III.5) - Нет. ## Ключевые решения (не пере-решать) - MCTS-движок — чистый модуль в packages/ai (без воркер-API); воркер — тонкая обёртка worker.ts с абстракцией порта. Однопоточно (без SharedArrayBuffer — COOP/COEP на шареде не гарантированы). - Плейаут — «лёгкая» политика, НЕ chooseMove этапа 3: тот сканирует всю доску через applyMove на каждый ход (~мс на ход) — в плейауте непозволительно. Лёгкая: дешёвая реакция на атари у последнего хода + случайные точки с фильтром своего глаза и лимитом попыток. Компромисс силы ради бюджета — зафиксирован здесь. - Бюджеты: {4: 300, 5: 800, 6: 1500} мс; ур.5 — дефолт между уровнями (спека задаёт только крайние) — записано в контракте. - Сериализация позиции в протоколе — списком ходов от пустой доски (воркер пересобирает и валидирует через applyMove), не снапшотом grid. - cancel → воркер отвечает result с лучшим найденным, не молчит и не error. - maxSimulations — override для детерминированных тестов. - Контракт — docs/INTERFACES.md (зафиксирован 2026-08-05 до кода). ## Объём этапа 4 1. packages/ai/src/mcts.ts — дерево UCT (c=1.4), ленивая экспансия, лёгкий плейаут, оценка scorePosition с komi, бюджет clock/shouldStop/maxSimulations, topMoves (до 3) для подсказки этапа 5. 2. packages/ai/src/worker-protocol.ts — типы WorkerRequest/WorkerResponse + пересборка BoardState из списка ходов (валидация, error при нелегальном). 3. packages/ai/src/worker.ts — тонкая обёртка порта (onmessage → choose → result/error; cancel → result с лучшим найденным). 4. index.ts — экспорты движка и протокола. ## Тесты (гейт приёмки этапа) - Детерминизм: maxSimulations=50 + фиксированный seed → идентичный move и topMoves в двух прогонах на одной позиции. - Легальность: ход MCTS легален на случайных позициях (запиненные seed'ы, 9×9 и 13×13); pass возвращается, только если легальных постановок нет или он лучший по визитам (в любом случае applyMove не падает). - Бюджет: фейк-clock (счётчик now()) — остановка по deadline; shouldStop() — остановка после N симуляций, ответ всегда с легальным ходом. - Качество (мягкий): MCTS ур.4 с maxSimulations=200 забирает группу в атари на сконструированной позиции (где эвристика ур.1 с rng 0.99 пропускает) — не эталон силы, а smoke «дерево реально ищет». - Протокол: фейк-порт — choose → result с тем же requestId; нелегальный список ходов → error; cancel активного → result, не error; cancel чужого id → без эффекта. - Полная партия 9×9 бот(MCTS ур.4, maxSimulations=100) против бота ур.3 — завершается до over; все ходы легальны (предпосылка критерия v1 №2). - Perf-smoke: 1500 мс бюджета → ≤ ~2.2 с фактически (overhead кроме симуляций мал); число симуляций за 300 мс на 9×9 — в отчёт. - Wiring: orphan-check обновить под новые экспорты. ## Критерий выхода Все тесты зелёные + `npm run gates` зелёный. Критерий v1 №3 («ур.6 < 2 с на среднем ноутбуке») — по бюджету 1500 мс + overhead; факт — в отчёт сессии. ## Пометки по факту реализации (2026-08-05) - Все пункты объёма и тестов выполнены; гейты зелёные (118 тестов, lint без предупреждений). Perf-факты: ~147 симуляций за 300 мс на 9×9; ур.6 на 13×13 — фактически ~1500 мс (бюджет 1500 мс + overhead < 5 мс). - Экспансия untried-детей — в детерминированном порядке legalMoves, первыми идут ходы со снятием камней (захваты разворачиваются в дерево раньше). Контракт порядок экспансии не оговаривает — эвристика внутри рамок UCT; без неё сигнал качество-smoke (захват группы за 200 симуляций) не выделяется на фоне шума плейаутов. - createWorkerHandler(port, deps?) — необязательный второй параметр WorkerDeps с инжектируемыми часами (в контракте «функция вида…»): нужен для детерминированных тестов протокола (фейк-часы, реентерабельный cancel из колбэка now()). - Качество-smoke: позиция «кольцо в атари + сплошные стены» — единственная из проверенных конструкций, где захват даёт устойчивый перекос winRate в случайных плейаутах (≈0.95 против ≈0.57). Seed запинен: при 200 симуляциях захват выбирается в ~75% seed'ов (при 800 — 12/12).