chore: восстановление репозитория из снапшота v0.3.1

Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sab.code.lab 2026-08-07 09:34:02 +02:00
commit 1c85091186
184 changed files with 33303 additions and 0 deletions

1127
docs/INTERFACES.md Normal file

File diff suppressed because it is too large Load diff

95
docs/PROJECT_STATE.md Normal file
View file

@ -0,0 +1,95 @@
# PROJECT_STATE — состояние проекта «Сайт обучения Го»
Обновлено: 2026-08-07 | Версия: 0.3.1 | Сессия: 11 (этап 11)
## Этапы (по мастер-промпту)
| Этап | Статус | Комментарий |
| -------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
| Шаг 0. Методология и каркас | завершено | Триаж — ADR-0001; каркас подтверждён владельцем молчанием (дефолт принят) |
| 1. Движок правил (packages/core) | завершено | 43/43 теста, gates зелёные; контракт — docs/INTERFACES.md; выбор алгоритма групп — ADR-0002 (flood fill) |
| 2. Доска (Canvas) | завершено | 11 слоёв render, reduceInput, TerritoryMap; тёмная тёплая тема |
| 3. ИИ ур. 13 (эвристика) | завершено | chooseMove, LEVEL_ATARI_PROBABILITY, детерминизм по seed |
| 4. ИИ ур. 46 (MCTS, Web Worker) | завершено | UCT c=1.4, бюджеты 300/800/1500 мс, воркер-протокол stateless |
| 5. Игровой интерфейс | завершено | /play: партии vs ИИ, подсказки, SGF; hotfix play.html |
| 6. Курс + цумэ-го | завершено | 10 уроков, 33 цумэ-го + 7 задач уроков, прогресс/серия дней; 413/413 тестов |
| 7. Полировка | завершено | .htaccess, клавиатура/ARIA, reduced-motion, отметка урока, вес ≤300 КБ; 431/431 |
| 8. (опц.) PHP+SQLite бэкенд | завершено | Личный кабинет email+пароль (ADR-0004), sync прогресса; smoke.sh зелёный; деплой — docs/deploy.md |
| 9. (опц.) KataGo ONNX | заморожен | Не начинать, пока этапы 17 не приняты |
| 8.1. Кабинет 2.0 | завершено | Регистрация отдельной страницей + SVG-капча, восстановление пароля токеном; smoke 58 проверок |
| 10. Дизайн-перенос «васи» | завершено | Светлая тема от Kimi WebSites натянута на каркас; тёмная тема удалена; 467/467; превью — новая версия |
| 11. Аудит контента + «Источники» | завершено | 85 утверждений: 70 подтв./10 расх./5 не найдено (docs/audit/); /istochniki.html; 10 правок уроков; 467/467 |
## Известные проблемы
- Кабинет в статическом превью показывает «сервер синхронизации
недоступен» — by design (API оживает только на хостинге с PHP).
- Восстановление пароля — через mail() (этап 8.1); доставляемость на
shared-хостинге хрупкая (SPF/DKIM): владельцу проверить тестовое
письмо при деплое (шаг в docs/deploy.md). Тема письма — сырой UTF-8
без RFC 2047: на части MTA может отображаться некорректно.
- PHP-часть вне npm-гейтов: smoke.sh локально (PHP из deb в /tmp) и на
хостинге; CI для PHP не настроен.
- Риски хостинга от бэкенд-кодера: Secure-cookie за TLS-прокси без
HTTPS=on; rate limit по REMOTE_ADDR общий за NAT; WAL на NFS-ФС может
быть медленным (busy_timeout=5000 смягчает).
- Критерии приёмки v1: №1 (тесты) и №4 (чистый клон/сборка) — зелёные
автоматически; №2 (партия vs ур.5), №3 (тайминги ур.3 <50мс, ур.6 <2с),
№5 (мобильный Safari) — ручные, ожидают владельца.
- Этап 6, ограничения контента v1: все задачи toPlay='black' (parseSgf
моделирует от пустой доски с чёрных; виджет умеет инверсию B/W, но
контент не использует); мотив снэпбэк невыразим (parseSgf падает на
occupied при повторной игре точки); тема «сеть (гэта)» в цумэ-го
отсутствует (покрыта текстом урока 2); у two-eyes задач авто-ответ
иногда тэнуки (пассивный соперник).
- task-session: goal проверяется после КАЖДОГО хода игрока, до авто-ответа
(фикс интеграции этапа 6; иначе транзиентные цели liberties недостижимы).
- Ссылки ведут на /play.html, сборка с build.format 'file' (hotfix после
этапа 5): статические превью-серверы без directory index давали 404 на /play.
На Apache с DirectoryIndex работали бы оба варианта, формат 'file' универсальнее.
- CI (.github/workflows/ci.yml) написан, но не проверен запуском — репозиторий
пока локальный. Первая проверка — после публикации на GitHub.
- packages/board без project reference на core (TS6310 при `tsc -b --noEmit`,
TS 5.9.3): импорт резолвится через workspace-симлинк. Пересмотреть при
обновлении TypeScript или смене скрипта typecheck.
- Реальный Worker не принимает cancel во время синхронного счёта (event loop);
семантика протокола реализована, но в этапе 5 UI решает: чанкование счёта
через setTimeout в воркере или terminate+пересоздание воркера (комментарий
в packages/ai/src/worker.ts).
- Плейаут MCTS ограничен ~147 сим/300 мс на 9×9: applyMove копирует
positionHashes каждый ход (O(n²) в плейауте). Ускорение — только через core,
отдельной задачей, если силы ур. 46 не хватит на приёмке.
- Astro 5.18.2 вместо 7.x: astro 6/7 требуют Node ≥22.12 (hardcoded), у нас
Node 20.20.2. Пересмотреть при обновлении Node на машине разработки.
- suggestDeadGroups (core) на разрежённых позициях предлагает «мёртвыми»
живые группы — грубость заявлена в контракте, ручная корректировка в UI
есть; кандидат на уточнение эвристики.
- Критерии v1 №2 (партия vs ур.5) и №5 (мобильный Safari) — ручная приёмка
владельцем после этапа 7.
- Суперко юнит-тест моделирует цикл >2 ходов через сконструированную историю
хэшей (настоящее двойное ко громоздко в фикстуре); механизм дополнительно
покрыт property-тестом. Отложено: построить настоящую позицию двойного ко
на 9×9 — низкий приоритет.
- Resign-узлы сериализуются в SGF как пас (в SGF нет хода «сдаюсь») —
задокументировано в packages/core/src/sgf.ts, осознанное ограничение.
- suggestDeadGroups — грубая эвристика, возможны ложные срабатывания на
открытом пространстве; ручная корректировка — за UI (этап 5).
- Коми движка 6,5 (19×19) расходится со стандартом китайских правил 7,5
(аудит этапа 11, SL ChineseRules/Komi). Урок 05 честно разводит «наши
партии» и «стандарт». Владельцу предложено решение (2026-08-07) — ответ
«без предпочтений»: оставляем 6,5 как есть; вопрос закрыт до нового
решения.
## Критические долги
- Нет.
## Планы (от владельца)
- Этап 12 (черновик, 2026-08-07): добавить в подвал сайта подпись
«сайт сделан sab:core» (формулировка подтверждена владельцем)
с логотипом; подпись кликабельна — URL владелец даст позже. Логотип
был в соседнем диалоге (сайт с самолётиками) — оркестратору недоступен;
владелец загрузит файл в чат. При старте: получить лого (лучше PNG/SVG
с прозрачным фоном) и URL; размещение — в футере рядом с privacy-note,
спокойный стиль без кричащих элементов.

9
docs/README.md Normal file
View file

@ -0,0 +1,9 @@
# Документация — индекс
Обновлено: 2026-08-05 | Версия: 0.1.0
- PROJECT_STATE.md — состояние этапов, проблемы, критические долги
- INTERFACES.md — контракты между модулями (сначала запись, потом код)
- architecture.md — обзор, слои, «Грабли проекта»
- decisions/ — ADR (не редактируются; оспаривание — новым ADR)
- plans/ — план текущей фазы с блоком «Ключевые решения (не пере-решать)»

27
docs/architecture.md Normal file
View file

@ -0,0 +1,27 @@
# Архитектура
Обновлено: 2026-08-05 | Версия: 0.1.0
## Обзор
Полностью клиентское приложение. Статическая сборка заливается на шаред-хостинг
по FTP. Движок правил и ИИ выполняются в браузере. Подробный дизайн появится
с кодом этапов 12; здесь фиксируются только решения уровня каркаса.
## Слои
- **packages/core** — движок правил. Чистый TypeScript, ноль импортов DOM и
воркер-API; Date.now()/Math.random() запрещены — часы и seeded RNG инжектируются.
- **(этап 2+) packages/board** — отрисовка Canvas 2D, чистая render-функция.
- **(этап 3+) packages/ai** — эвристика и MCTS; в браузере исполняется в Web Worker.
- **(этап 5+) apps/web** — Astro-сайт, UI, курс, localStorage-прогресс.
Правило изоляции: core не импортирует ничего из board/ai/web — проверяется
сборкой и lint-границами (этап 1).
## Грабли проекта
> Живой раздел: новые грабли — сюда, с источником (литания 0.7).
> Копия prestart-litany.md в корне — read-only архив, её не редактируют.
- Пока пусто.

283
docs/audit/01-03.md Normal file
View file

@ -0,0 +1,283 @@
# Аудит уроков 0103: проверка фактов по авторитетным источникам
Дата: аудит R1. Метод: каждое утверждение проверено открытием страницы источника
(web_open_url), приводятся дословные цитаты. Источники: правила AGA (краткая версия,
зеркало BGA), British Go Association (britgo.org), Sensei's Library (senseis.xmp.net),
библиотека Российской федерации го (rusgolib.gofederation.ru).
**Итог: 28 проверяемых утверждений — 24 ПОДТВЕРЖДЕНО (2 из них с оговорками),
4 РАСХОЖДЕНИЕ, 0 НЕ НАЙДЕНО.**
---
## Урок 01 «Дамэ и захват»
### 1.1. Дамэ — свободные соседние пункты по линиям; диагонали не считаются
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: правила AGA, краткая версия (зеркало BGA), https://www.britgo.org/rules/agashort.html
> «A liberty of a stone is a vacant, horizontally or vertically adjacent intersection. <…> The intersections diagonal to the stone are not adjacent and are not counted as liberties of the stone.»
> Дополнительно Sensei's Library, https://senseis.xmp.net/?Dame
> «dame is an empty point or liberty adjacent to a stone or connected group of stones».
### 1.2. Нет дамэ — камень/группа снимается с доски
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?RulesOfGoIntroductory
> «If there are no empty points next to a stone or a string of stones (i.e. no liberties), the stones are removed from the board.»
### 1.3. Одинокий камень: в центре 4 дамэ, на краю 3, в углу 2
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html
> «A single stone in the middle of an empty board has four liberties <…> A single stone on a side intersection has a maximum of three liberties; a single stone in the corner has a maximum of two liberties.»
> Дополнительно BGA, https://www.britgo.org/intro/intro2.html
> «A single stone on the side has three liberties, and a stone in the corner has only two liberties.»
### 1.4. Группа делит дамы на всех; цепочка из двух камней в центре — 6 дамэ
**Вердикт: ПОДТВЕРЖДЕНО** (правило подтверждено источником; число 6 — прямое следствие: 4+42 общих пункта = 6).
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html
> «The liberties of a string of stones are the liberties of all the individual stones in that string.»
### 1.5. Захватывается группа целиком
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: BGA, https://www.britgo.org/intro/intro2.html
> «As far as capturing is concerned, a string of stones is treated as a single unit. <…> a string is captured when all of its liberties are occupied by enemy stones.»
> Дополнительно Sensei's Library: «As a string, the stones stand or fall together.» (https://senseis.xmp.net/?RulesOfGoIntroductory)
### 1.6. Захваченные камни становятся пленниками
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html
> «Such stones become prisoners of the capturing player.»
> Дополнительно Sensei's Library: «Captured stones are called prisoners or captives.» (https://senseis.xmp.net/?Capture)
### 1.7. Атари — ситуация, когда у группы осталось ровно одно дамэ
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Atari
> «The state of a stone or group of stones that has only one liberty.»
### 1.8. «Опытные игроки вслух говорят "атари", когда ставят его»
**Вердикт: РАСХОЖДЕНИЕ.**
Источник: Sensei's Library, https://senseis.xmp.net/?CallingOutAtari
> «It is unusual among experienced players and very unusual at tournaments.»
> Как должно быть: вслух говорить «атари» принято в обучающих партиях с новичками; среди
> опытных игроков это необычно и многими считается дурным тоном (ранние книги — Korschelt,
> Lasker, Smith — описывали это как старый японский этикет). Примечание: глоссарий BGA
> (https://www.britgo.org/general/definitions.html) говорит «A verbal warning is often issued
> when placing an opponent into ate» — т.е. традиция существует, но как норма поведения
> «опытных игроков» утверждение урока неверно.
---
## Урок 02 «Атари, лестница, сеть»
### 2.1. Атари — одно дамэ; поставить атари = пригрозить захватом на следующий ход
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: глоссарий BGA, https://www.britgo.org/general/definitions.html
> «Atari (Ate): An immediate threat to capture; a single liberty remains.»
### 2.2. «Атари… заставляет противника отвечать»
**Вердикт: РАСХОЖДЕНИЕ (мягкое).**
Источник: Sensei's Library, https://senseis.xmp.net/?CallingOutAtari
> «atari can be ignored quite deliberately to your advantage» (и: «It suggests that atari has to be answered» — отмечено как вредное ложное впечатление).
> Как должно быть: атари — это угроза, на которую противник аще всего_ отвечает, но отвечать
> не обязательно; встречные атари, сброс ценности и т.п. — нормальная часть игры. Впрочем,
> сам урок ниже верно советует спрашивать «а что я получу, если он спасётся?», так что
> формулировку стоит смягчить, а не удалить. (Оценочное «самое сильное оружие новичка» —
> не факт, мнение; не проверялось.)
### 2.3. Лестница — серия атари, при которой убегающий всякий раз получает лишь один путь/одно новое дамэ; зигзаг через доску
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Ladder
> «At each step the attacker plays atari <…> and then the defender runs away, but can only gain one more liberty. <…> ladders normally follow a zigzag pattern across the board».
### 2.4. Лестница срывается камнем противника на её пути (по диагонали бега)
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Ladder
> «Suppose, however, that there is a stone in the path of the ladder <…> The ladder is broken. <…> This is therefore a disaster for Black.»
> Дополнительно (рус. терминология): rusgolib.gofederation.ru («Школьная Компьютерра», А. Битман, В. Медведев):
> «если на пути движущейся лестницы вдруг окажется камень убегающей стороны, то результат ситё может оказаться неблагоприятным для атакующего».
### 2.5. Лестничная опора называется «сирэ»
**Вердикт: РАСХОЖДЕНИЕ (термин).**
Термин «сирэ» в го-источниках не обнаружен (поиск по «сирэ го лестница» результатов по игре
не дал). Устоявшиеся названия этого камня:
- англ./яп.: **ladder breaker / shicho-atari**. Глоссарий BGA, https://www.britgo.org/general/definitions.html:
> «Shicho-atari: Ladder breaker. A stone played in the path of a potential shicho, threatening to make it fail.»
- рус.: **ситё-прерыватель**. rusgolib.gofederation.ru:
> «можно с выгодой поставить свой камень — так называемый ситё-прерыватель в направлении движения лестницы».
> Как должно быть: «ситё-атари» / «ситё-прерыватель» (ladder breaker). Само описание механизма
> в уроке верно, неверен только термин.
### 2.6. «Лестница либо захватывает, либо катастрофически проигрывает — середины нет»
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Ladder
> «we can plainly see that a ladder is little more than a one-sided fight for one side or the other» (проигрышная лестница названа «a disaster» для обеих сторон в примерах).
### 2.7. У камня на первой линии три дамэ; прижатая к краю лестница кончается захватом
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html
> «A single stone on a side intersection has a maximum of three liberties».
> Источник: Sensei's Library, https://senseis.xmp.net/?Ladder
> «The process stops when the defender can run no further (is captured) or gets more than two liberties».
### 2.8. Сеть (гэта) — тихий захват без атари, заранее закрывающий пути к бегству
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Geta
> «A net (also frequently called geta) is a technique where one or more stones are captured by enclosing them in a 'net' of stones which has holes in it, all of which the attacker can block.»
> Дополнительно глоссарий BGA: «Geta: A method of capturing an enemy stone; a net trap.»
### 2.9. «Классическая сеть ставится через один пункт от цели (конём, как в шахматах)»
**Вердикт: ПОДТВЕРЖДЕНО с оговоркой.**
Сеть ходом коня (keima) — стандартная форма: Sensei's Library, https://senseis.xmp.net/?Geta
> «Sometimes the solution is to make a looser net with a keima, when a tight net at a doesn't work» (см. также страницу «Knight's move net» в том же разделе).
> Глоссарий BGA: «Keima: Knight's move extension.»
> Оговорка: Sensei's называет keima-сеть «более свободной» разновидностью; сети бывают и тесными.
> Формулировка «классическая сеть — всегда конём» — допустимое упрощение для новичков,
> но не универсальное правило.
### 2.10. «Если лестница сломана опорой — ищите сеть»
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: rusgolib.gofederation.ru:
> «белый камень… не удается взять в ситё из-за присутствия ситё-прерывателя в пункте А. В данной ситуации срабатывает другой тактический прием — под названием гэта, или западня.»
> (У Sensei's Library та же связка: «Net versus ladder», «Ladder and loose ladder» — https://senseis.xmp.net/?Geta)
---
## Урок 03 «Самоубийство и ко»
### 3.1. Ход, после которого у своей группы не остаётся дамэ, запрещён
**Вердикт: ПОДТВЕРЖДЕНО с оговоркой.**
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html
> «It is illegal for a player to move so as to create a string of his or her own stones which is completely surrounded (without liberties) after any surrounded opposing stones are captured.»
> Источник: Sensei's Library, https://senseis.xmp.net/?Suicide
> «Suicide moves are forbidden under Japanese rules, Chinese rules, and AGA rules.»
> Оговорка: запрет не универсален — «Suicides are allowed in some other rulesets, such as Ing
> rules, New Zealand rules and Tromp-Taylor rules.» Для учебного курса (стандартные
> японские/AGA-правила) формулировка урока корректна, но можно дать сноску.
### 3.2. «Такой ход не регистрируется, придётся ходить в другое место»
**Вердикт: РАСХОЖДЕНИЕ (мягкое).**
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html, правило 8:
> «If a player makes an illegal move, it shall be taken back, treated as a pass, and a pass stone exchanged.»
> Как должно быть: по правилам AGA нелегальный ход отменяется и засчитывается как **пас**
> (а не «переход» в другое место); в японских правилах нарушение правил ведёт к техническому
> поражению. «Перехожу в другое место» — лишь любительская практика дружеских игр.
### 3.3. Исключение: ход в «запретный» пункт легален, если он снимает группу противника
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: BGA, https://www.britgo.org/intro/intro2.html
> «A player may not self-capture <…> unless, as a result, one or more of the stones surrounding it is captured.»
> Дополнительно BGA Quick Reference, https://www.britgo.org/files/rules/GoQuickRef.pdf (§9):
> «The only exception to this is if you make a move that gives both players stones or chains with no liberties. If this happens, you take off the other person's stones, but your own stones stay on the board.»
### 3.4. Ко: пара взаимных захватов одного камня повторялась бы бесконечно без правила
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Ko
> «It describes a situation where two alternating single stone captures would repeat the original board position. The alternating captures could repeat indefinitely, preventing the game from ending.»
### 3.5. «Ко» по-японски — «вечность»
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: правила AGA (краткая версия), https://www.britgo.org/rules/agashort.html
> «("Ko" is the Japanese Buddhist word for eternity.)»
> Дополнительно BGA, https://www.britgo.org/intro/intro2.html:
> «This pattern of stones is called ko - a Japanese term meaning eternity.»
> Примечание: этимология спорна (Sensei's Library, раздел Ko etymology: «Unresolved»; иероглиф 劫
> восходит к санскритскому «кальпа»), но перевод «вечность» — стандартный в англоязычной
> литературе, так что утверждение урока допустимо.
### 3.6. Правило ко: немедленный обратный захват запрещён; сначала ход в другом месте; потом запрет снимается
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Ko
> «If one player captures the ko, the opponent is prohibited from recapturing the ko immediately. <…> Instead White has to play elsewhere. <…> the recapture restriction is only valid for the next turn.»
> Дополнительно AGA (краткая версия): «After the first capture, the player moving next may not recapture immediately <…> that player must play elsewhere on the board (or pass).»
### 3.7. Ко-угроза — ход в другом месте, требующий ответа; после ответа можно забрать ко
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?KoThreat
> «any move that threatens a large follow-up may be considered a ko threat. <…> If they do answer, it will be legal for you to recapture the ko».
> Дополнительно глоссарий BGA: «Ko threat: Intervening move (that one hopes will force a reply) before a ko can be recaptured.»
### 3.8. «Выигрывает ко тот, у кого больше угроз»
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?KoThreat
> «The player with more ko threats available can often play very aggressively, since he is not afraid to start ko fights and can reasonably expect to win any ko that his opponent starts.»
### 3.9. «Иногда выгоднее "продать" ко, получив два хода подряд в другом месте»
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?Ko
> «That way black gets compensation for not winning the ko by getting two moves elsewhere.»
> Дополнительно https://senseis.xmp.net/?KoThreat: «you can follow through on your threat and thereby gain compensation for losing the ko».
### 3.10. «Не отвечайте на мелкие угрозы, если ко дороже»
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: Sensei's Library, https://senseis.xmp.net/?KoThreat
> «sometimes a ko threat whose value is smaller than the value of the ko itself is described as "not a ko threat"» (принцип сравнения ценности ко и угрозы).
---
## Сводка
| Урок | Подтверждено | Расхождение | Не найдено |
| --------- | ------------------- | ------------------------------------------------------ | ---------- |
| 01 | 7 | 1 (1.8 — «опытные говорят атари вслух») | 0 |
| 02 | 8 (2.9 с оговоркой) | 2 (2.2 — «заставляет отвечать»; 2.5 — термин «сирэ») | 0 |
| 03 | 9 (3.1 с оговоркой) | 1 (3.2 — нелегальный ход засчитывается как пас по AGA) | 0 |
| **Итого** | **24** | **4** | **0** |
Оценочные/риторические фразы («атари — самое сильное оружие новичка», «всё в Го построено
на подсчёте дамэ», «край доски — лучший друг лестницы») фактическими утверждениями
не считались и не вердиктились.

493
docs/audit/04-07.md Normal file
View file

@ -0,0 +1,493 @@
# Аудит уроков 0407
Дата: 2026 (сеанс аудита). Метод: каждое утверждение сверено с авторитетным
источником, страница РЕАЛЬНО открыта инструментом, приведена дословная цитата.
Вердикты: ПОДТВЕРЖДЕНО | НЕ НАЙДЕНО | РАСХОЖДЕНИЕ.
Основные источники: Sensei's Library (senseis.xmp.net), AGA Concise Rules of Go
(зеркало cs.cmu.edu, текст предоставлен Fred Hansen для AGA).
---
## Урок 04. Глаза: жизнь и смерть
### 4.1. Глаз — один или несколько соседних пустых пунктов, окружённых камнями одного цвета
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Eye
> «In general, what Go players call an eye is _an empty space surrounded by
> stones of one colour_, the simplest forms of which are the single point eyes…
> However, larger surrounded spaces can also be called eyes. More generally we
> call these an eyespace.»
### 4.2. Ход противника в глаз — самоубийство; сыграть туда можно, лишь сняв внешние дамы (поставив группу в атари)
**ПОДТВЕРЖДЕНО.**
Источник: https://www.cs.cmu.edu/~wjh/go/rules/AGA.concise.html (AGA Concise Rules, правило 5)
> «It is illegal for a player to move so as to create a string of his or her
> own stones which is completely surrounded (without liberties) after any
> surrounded opposing stones are captured.»
> Источник 2: https://senseis.xmp.net/?TwoEyes
> «A white stone placed on either of them would be disconnected from the other
> white stones and would have no liberties… Such a move by White would thus be
> illegal.»
### 4.3. Два отдельных глаза — группа неуязвима («вечная жизнь»)
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?TwoEyes
> «To be safe from capture, a group of stones must in general have two eyes…
> if a group has at least two separate liberties which cannot be removed, the
> group cannot be killed and is alive.»
> «Neither of the two circled points can be occupied by White without the
> other… it is impossible to remove the black group from the board (without
> Black filling one eye).»
### 4.4. Один большой глаз — ещё не жизнь; пространство нужно разделять на две независимые области
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?TwoEyes
> «Note that sometimes a group may have a single eye with more than one point
> in it, but that does not necessarily make the group alive: _separate_ eyes
> are required.»
### 4.5. Ложный глаз: камни вокруг него не связаны (касаются по диагонали), их можно поставить в атари и занять «глаз»
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?FalseEye
> «A false eye is something which looks like an eye, but shouldn't be counted
> as part of the group's eye space because some of the surrounding stones can
> be put into atari.»
> «Notice that the marked black stones have an incomplete connection with the
> rest of the group… If White fills up the exterior liberties… the three black
> stones are in atari. If Black connects at a, the "eye" disappears.»
### 4.6. Решение задачи жизни и смерти — жизненная точка (vital point)
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?VitalPoint
> «Vital point can refer to the key point in the context of a life and death
> problem. This has been traditionally called a nakade, while "vital point" is
> aiming to translate the Japanese term "kyuusho".»
### 4.7. «Ищите центр симметрии формы — чаще всего он и есть ответ»
**ПОДТВЕРЖДЕНО (как пословица-эвристика, с оговоркой).**
Источник: https://senseis.xmp.net/?PlayOnThePointOfSymmetry
> «When right and left have the same shape, there is a play in the center.»
> «Many symmetrical positions have asymmetrical solutions, so this proverb
> should only be taken as a suggestion for where to look.»
> Оговорка: источник подчёркивает, что пословица — лишь подсказка, а не правило;
> в уроке это смягчено словом «чаще всего», что допустимо.
---
## Урок 05. Подсчёт и конец партии
### 5.1. Два паса подряд — конец партии
**ПОДТВЕРЖДЕНО.**
Источник: https://www.cs.cmu.edu/~wjh/go/rules/AGA.concise.html (правило 9)
> «Two consecutive passes normally signal the end of the game.»
> Источник 2: https://senseis.xmp.net/?Pass
> «If both players pass on their consecutive moves the game usually ends; and
> then scoring and counting starts.»
### 5.2. «Пасовать позже — тоже ошибка: ход в свою территорию просто уменьшает её»
**РАСХОЖДЕНИЕ (в рамках собственной системы правил курса).**
Курс объявляет китайские правила (см. 5.5), а при площадном подсчёте ход в свою
территорию счёт НЕ меняет; «1 очко» верно только для японских правил.
Источник: https://senseis.xmp.net/?ChineseRules
> «Cost of moving in one's territory: 0 points»
> Источник 2: https://senseis.xmp.net/?JapaneseRules
> «Cost of moving in one's territory: 1 point»
> Как должно быть: «ход в свою территорию уменьшает её по японским правилам; по
> китайским (нашим) — счёт не меняется, но ход впустую тратит время/инициативу».
> В тексте урока это противоречит собственному разделу «Подсчёт по-китайски»,
> где верно сказано обратное.
### 5.3. Мёртвые камни убираются с доски по согласию; при несогласии партия продолжается (доигрывание)
**ПОДТВЕРЖДЕНО.**
Источник: https://www.cs.cmu.edu/~wjh/go/rules/AGA.concise.html (правила 910)
> «Any stones which the players agree could not escape capture if the game
> continued… are termed dead stones… they are removed from the board as
> prisoners… If there is a disagreement over the status of some group or
> groups, play is resumed as specified in Rule 10.»
> Источник 2: https://senseis.xmp.net/?AreaScoring
> «Practically, players can agree upon obviously dead stones and remove them.
> After playout or agreement, the score is counted.»
> Источник 3: https://senseis.xmp.net/?ChineseRules
> «Life and death settled by: Game resumption for online play.»
### 5.4. Территория — пустые пункты, окружённые живыми камнями одного цвета; каждый пункт — очко
**ПОДТВЕРЖДЕНО.**
Источник: https://www.cs.cmu.edu/~wjh/go/rules/AGA.concise.html (правило 12)
> «Those empty points on the board which are entirely surrounded by live
> stones of a single color are considered the territory of the player of that
> color.»
### 5.5. Китайский (площадной) подсчёт: очки = живые камни на доске + территория
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?AreaScoring
> «In area scoring, your score is the sum of two components — the number of
> empty points only your stones surround and the number of your stones on the
> board… Area scoring is used in certain rulesets, notably AGA rules, Chinese
> Rules…»
> Источник 2: https://senseis.xmp.net/?ChineseRules
> «Scoring method: Area scoring»
### 5.6. Коми 6,5 очка на большой доске (при заявленных китайских правилах)
**РАСХОЖДЕНИЕ.**
Стандартное коми китайских правил — 7,5; 6,5 — стандарт японских/корейских
правил (территориальный подсчёт).
Источник: https://senseis.xmp.net/?ChineseRules
> «Komi: 7.5»
> Источник 2: https://senseis.xmp.net/?Komi
> «The usual komi in China, where area scoring is used, was formerly equal to
> the 5.5 used in Japan, but was changed to 7.5.»
> «Today, the standard komi in Japan, where territory scoring is used, is 6.5
> points, introduced September 2002.»
> Источник 3: https://senseis.xmp.net/?JapaneseRules
> «Komi: 6.5»
> Как должно быть: «коми 7,5 очка на большой доске (китайские правила)».
### 5.7. Коми 5,5 на доске 9×9
**ПОДТВЕРЖДЕНО (как распространённая практика, не единый стандарт).**
Источник: https://senseis.xmp.net/?HandicapForSmallerBoardSizes
(«Old Japanese Recommendation», Исикура Нобору, журнал Igo Kurabu, ~1985)
> «Around 1985 there was an article in the Japanese magazine Igo Kurabu by
> Ishikura Noboru (now 9p) on komi and handicaps for both 9x9 and 13x13
> boards… Difference in strength 0: 9x9 — komi 5.5»
> Источник 2 (та же страница, AGA Handicaps 2004):
> «Rating Difference 0.00.5: 19x19 — 7.5 komi, 13x13 — 5.5 komi,
> 9x9 — 5.5 komi»
> Оговорка: единого официального стандарта для 9×9 нет (встречаются 6.5 и 7),
> но 5.5 — документированная общепринятая практика.
### 5.8. Пол-очка в коми исключает ничью
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Komi
> «To prevent a drawn game in the case of jigo, the komi is commonly set to a
> fractional value such as 6.5.»
### 5.9. По китайским правилам ход внутрь своей территории не меняет счёта, по японским — потерял бы очко
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?ChineseRules
> «Cost of moving in one's territory: 0 points»
> Источник 2: https://senseis.xmp.net/?JapaneseRules
> «Cost of moving in one's territory: 1 point»
### 5.10. Арифметика примера (чёрные 50, белые 44 + коми 5,5 = 49,5; перевес 0,5)
**ПОДТВЕРЖДЕНО (проверка вычислением).** 30+20=50; 28+16=44; 44+5,5=49,5;
5049,5=0,5. Ошибок нет.
---
## Урок 06. Связь и разрезание
### 6.1. «Соединяйте свои группы и разрезайте чужие» — главная тактическая идея Го
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Cut
> «Cutting and connecting is a central concept in Go. You are normally well
> advised to keep your own groups connected and cut your opponent's groups
> apart.»
### 6.2. Камни, стоящие вплотную по линии, связаны абсолютно — их нельзя разрезать
**ПОДТВЕРЖДЕНО.**
Источник: https://www.cs.cmu.edu/~wjh/go/rules/AGA.concise.html (правило 5)
> «Stones of the same color are said to be connected if they are adjacent
> along horizontal or vertical lines on the board… Two stones are part of the
> same string if they are linked by a chain of connected stones of the same
> color.»
> (Строка снимается целиком; «разрезать» смежные камни по линии невозможно —
> между ними нет пустого пункта.)
### 6.3. Иккэн (иккэн-тоби) — ход через один пункт (one-point jump)
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?IkkenTobi (редирект на «One-point jump»)
> «Common in running fights, the one-point jump moves out quickly… but is
> potentially vulnerable to being cut depending on the surrounding position
> (see cutting the one-point jump).»
> Источник 2: https://senseis.xmp.net/?Moyo
> «…further extended with a one-point-jump (Ikken Tobi)…»
### 6.4. Связь через один пункт надёжна лишь условно — противник может попытаться вклиниться
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?OneSpaceJump
> «…but is potentially vulnerable to being cut depending on the surrounding
> position…»
### 6.5. Косуми — диагональный ход
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Kosumi
> «Kosumi is a Japanese go term adopted into English. It is often translated
> as diagonal or diagonal move.»
### 6.6. «Диагональная "связь" (косуми) вообще не связь… противник может разрезать её первым»
**РАСХОЖДЕНИЕ (частичное).**
Верно, что косуми — не физическая связь. Но утверждение «противник может
разрезать её первым» как общее правило неверно: изолированное косуми при
попеременной игре локально НЕразрезаемо (миаи); разрезание возможно только при
подходящем окружении.
Источник: https://senseis.xmp.net/?Kosumi
> «The kosumi is not yet physically connected but with locally alternating
> play it can not be cut: if White tries to cut at a, Black plays at b and
> becomes connected and vice versa. These two points are prime examples of
> miai.»
> Источник 2: https://senseis.xmp.net/?Cut
> «In a narrow sense, a cut is a method of cutting off what appears to be a
> safe connection, such as kosumi, one-space jump or keima… the problem is to
> choose the method of cutting according to the surrounding conditions.»
> Как должно быть: «косуми — не физическая связь; само по себе оно неразрезаемо
> (миаи), но при сильных камнях противника вокруг его можно атаковать в
> уязвимые пункты».
### 6.7. «Захват разрезающего камня — связь восстановится сама собой»
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Cut
> «The Black stones will only be able to connect again if the cutting stone is
> captured.»
### 6.8. Разрезающий ход должен сам выжить
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Cut
> «It is worth noting that cutting stones are often themselves separated from
> their group, so if you are cutting two groups to capture one of them, you
> must make sure that your cutting stones will not get captured first.»
### 6.9. «Соединение снизу — соединиться по первой линии, под защитой края»
**НЕ НАЙДЕНО.**
Приём «connect underneath» существует (упоминается в литературе по тэсудзи),
но открыть авторитетную страницу с дословным подтверждением не удалось
(страница Sensei's Library по соединению снизу не открылась за несколько
попыток — таймауты).
---
## Урок 07. Углы, стороны, центр
### 7.1. Край доски работает как готовая стена; порядок дебюта: углы → стороны → центр
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?CornersThenSidesThenCenter
> «Making life, a base or territory is supported by a player's stones and the
> edges of a board.»
> «It takes only two walls of stones to surround territory in the corners,
> and three on the sides, compared to four in the center»
> (там же приведена пословица «Corner is gold, side is silver, center is
> grass»).
> Источник 2: https://senseis.xmp.net/?HighAndLowMoves
> «According to traditionally accepted views, the corners and sides of the
> board earn the most territory. The center is generally thought to earn the
> least.»
### 7.2. Числа: угол — 23 камня, сторона — 45, центр — 810 для ограждения одинаковой территории
**НЕ НАЙДЕНО (а каноническая количественная формулировка иная).**
Авторитетных источников с числами «23 / 45 / 810 камней» не найдено.
Sensei's Library приводит другие канонические числа: 2/3/4 стены (угол/сторона/
центр) и минимум 6/8/10 камней для жизни с двумя глазами.
Источник: https://senseis.xmp.net/?CornersThenSidesThenCenter
> «It takes only two walls of stones to surround territory in the corners,
> and three on the sides, compared to four in the center»
> «8 stones, 16 points — two eyes requires at least 6 stones [угол];
> 8 stones, 8 points — 2 eyes requires at least 8 stones [сторона];
> 8 stones, 4 points — 2 eyes requires at least 10 stones [центр]»
> Рекомендация: заменить числа на канонические («две стены в углу, три на
> стороне, четыре в центре»; «для жизни минимум 6/8/10 камней»).
### 7.3. Первая линия: камень на ней имеет три дамэ; в дебюте туда играть не стоит
**ПОДТВЕРЖДЕНО (по сути); термин «линия смерти» — НЕ НАЙДЕНО.**
Про три дамэ: https://www.cs.cmu.edu/~wjh/go/rules/AGA.concise.html (правило 5)
> «A single stone on a side intersection has a maximum of three liberties; a
> single stone in the corner has a maximum of two liberties.»
> Про нежелательность первой линии: https://senseis.xmp.net/?KnowYourLines
> «The first line is the edge of the board. Playing unsupported stones on the
> first line is a typical novice's mistake. Moves on the first line are common
> in the endgame, and in life and death.»
> Термин «линия смерти» (line of death) в открытых авторитетных источниках не
> обнаружен: Sensei's Library именует «линией поражения» вторую линию, первой
> линии специального названия не даёт. Термин распространён в русскоязычной
> педагогике, но источником не подтверждён.
### 7.4. Вторая линия — «линия поражения»
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?KnowYourLines
> «Crawling along the second line to make territory is inefficient, therefore
> it is also called "the line of defeat".»
### 7.5. Третья линия — «линия территории»
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?ThirdLine
> «On a 19x19 goban, it is said that the third line is the line of territory.»
> Источник 2: https://senseis.xmp.net/?KnowYourLines
> «Territorial moves are on the third line, therefore the third line is also
> called the "line of territory".»
### 7.6. Четвёртая линия — «линия влияния»
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?FourthLine
> «On a 19x19 goban, it is said that the fourth line is the line of
> influence.»
### 7.7. Классический баланс дебюта — игра на третьей и четвёртой линиях
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?HighAndLowMoves
> «The concept of High and Low moves is fundamental to Go Strategy. With high
> we usually refer to the fourth line, while low refers to the third line.
> The proverb says: "High move (4th line) for influence, low move (3rd line)
> for territory".»
### 7.8. 3-3 (сан-сан): надёжно забирает угол, но низко, мало влияния
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?SanSan
> «The 3-3 point, on the third line both horizontally and vertically, is a
> good point, in that it makes territory in the corner, but it is still quite
> low and makes for a slow development.»
> «…a play on the 3-3 point in itself can be considered as decisive: no
> immediate further play is required to settle the corner.»
### 7.9. 3-4 (комоку): самый популярный первый ход; угол почти ваш, остаётся гибкость
**ПОДТВЕРЖДЕНО (с исторической оговоркой).**
Источник: https://senseis.xmp.net/?Komoku
> «Like the 3-3 point, the 3-4 point (komoku) more or less secures the
> corner. At the same time, it also starts taking a look outward.»
> «The 3-4 point has a very rich history in Go literature as it was the most
> common opening play in the corner throughout the classical and modern
> periods of Go in Japan, up to about 1975.»
> Оговорка: «самый популярный» верно для классического и значительной части
> современного периода; в эпоху ИИ (после 2016) первые ходы 4-4 и 3-3 не менее
> частотны. Для курса новичков формулировка допустима.
### 7.10. 4-4 (хоси): звёздный пункт; угол можно оспорить вторжением 3-3; камень сильнее тянется к центру
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Hoshi
> «Star points (J. hoshi, lit. "star") are the nine points on a 19x19 go
> board marked by small dots… the 4-4 point (corner star)…»
> Источник 2: https://senseis.xmp.net/?44Point33Invasion
> «The 4-4 Point 3-3 invasion is a common and popular joseki… The 3-3 point
> is the vital point of the corner, and it is easy for the invader to live
> underneath with a corner territory worth ~10 points.»
> Источник 3: https://senseis.xmp.net/?Takamoku (сравнение)
> «As an opening play, the 4-5 point is more center-oriented than the 3-5
> point, and less corner-oriented than the 4-4 point.»
### 7.11. 3-5 (мокухадзуси) — уклон в сторону; 4-5 (такамоку) — влияние; оба реже
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?MokuHazushi
> «The 3-5 or 5-3 point is a classical first play in the corner. It is not a
> common move in modern top-level games… this move is more directed to the
> side.»
> Источник 2: https://senseis.xmp.net/?Takamoku
> «…it does little to protect the corner. The compensation of course is that
> it is strong towards the center… he will go for influence and large
> frameworks…»
> «…has dropped out of fashion with most modern players.»
### 7.12. Мойо — большие рамки (потенциальная территория)
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Moyo
> «A framework, or moyo, is an area where one player has a large influence,
> and which potentially could become that player's territory.»
### 7.13. «Центр — это сила, а не земля» (центр не даёт много территории напрямую)
**ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?HighAndLowMoves
> «According to traditionally accepted views, the corners and sides of the
> board earn the most territory. The center is generally thought to earn the
> least.»
---
## Итоговая статистика
- ПОДТВЕРЖДЕНО: 28 утверждений
- РАСХОЖДЕНИЕ: 3 утверждения (5.2, 5.6, 6.6)
- НЕ НАЙДЕНО: 3 утверждения (6.9, 7.2, термин «линия смерти» в 7.3)
### Список расхождений
1. **Урок 05, «ход в свою территорию уменьшает её»** — неверно для заявленных
китайских правил (cost = 0), верно только для японских (cost = 1).
Внутренне противоречит разделу «Подсчёт по-китайски» того же урока.
2. **Урок 05, коми 6,5 на большой доске** — по китайским правилам стандарт 7,5
(6,5 — японские/корейские правила).
3. **Урок 06, «косуми… противник может разрезать её первым»** — изолированное
косуми локально неразрезаемо (миаи); разрезание зависит от окружения.
### Список «не найдено»
1. **Урок 06, «соединение снизу по первой линии»** — не удалось открыть
авторитетную страницу (таймауты SL).
2. **Урок 07, числа «угол 23 / сторона 45 / центр 810 камней»** — источник
не найден; каноническая версия SL: 2/3/4 стены и минимум 6/8/10 камней для
жизни.
3. **Урок 07, термин «линия смерти» для первой линии** — в открытых
авторитетных источниках не подтверждён (SL называет «линией поражения»
вторую линию).

426
docs/audit/08-10.md Normal file
View file

@ -0,0 +1,426 @@
# Аудит уроков 0810
Проверены файлы:
- `apps/web/src/content/lessons/08-bazovye-dzyoseki.md`
- `apps/web/src/content/lessons/09-forma.md`
- `apps/web/src/content/lessons/10-prostoe-yose.md`
Метод: каждое фактическое утверждение сверено с авторитетными источниками
(Sensei's Library — senseis.xmp.net). Все процитированные страницы реально
открыты инструментом web_open_url; исключение помечено явно (страница
?33Point многократно не отвечала на web_open_url — таймауты — и была
прочитана прямым HTTP-запросом; цитата приведена из фактически полученного
текста страницы). gobase.org на момент аудита был недоступен (соединение не
устанавливается), поэтому дзёсэки подтверждались по Sensei's Library.
Вердикты: ПОДТВЕРЖДЕНО | НЕ НАЙДЕНО | РАСХОЖДЕНИЕ.
---
## Урок 08. Базовые дзёсэки
### 8.1. Определение дзёсэки
**Утверждение:** «Дзёсэки — устоявшаяся "правильная" последовательность
ходов в углу... Итог дзёсэки считается равным (или почти равным) для обеих
сторон: один получает угол, другой — стену или инициативу».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Joseki
> "Joseki ... usually referring to standard sequences of moves played out in a
> corner that result in a locally even exchange."
Там же подтверждается размен «угол против влияния/инициативы»:
> "Proper evaluation of joseki considers the trade-offs between: sente and
> gote, territory and influence, speed and solidity..."
### 8.2. Дзёсэки 1: занятие угла 3-3
**Утверждение:** «Простейший способ забрать угол — поставить камень прямо на
пункт 3-3... Чёрный камень на C3 почти неуязвим: у него сразу задел на
угол... белые обычно давят снаружи, а чёрные ползут по второй-третьей линии,
выстраивая жизнь. Идея: угол — мой, влияние — ваше».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?33Point (страница прочитана прямым
HTTP-запросом; web_open_url на неё стабильно отвечал таймаутом)
> "The 3-3 point ... makes territory in the corner, but it is still quite low
> and makes for a slow development. However, a play on the 3-3 point in itself
> can be considered as decisive: no immediate further play is required to
> settle the corner. ... For White, the shoulder hit at e, taking influence in
> the centre, is quite common"
Размен «земля против влияния» для пункта 3-3 подтверждён также здесь:
Источник: https://senseis.xmp.net/?44PointJosekis
> "The 3-3 invasion emphasizes solid territory in exchange for a significant
> loss of influence."
### 8.3. Дзёсэки 2: «ханэ у подошвы 3-4»
**Утверждение:** «Чёрные заняли угол ходом 3-4, белые подошли. Классический
ответ — ханэ (ход вокруг вражеского камня с изгибом), затем спокойное
отступление... Чёрные укрепляют угол, белые получают толщину снаружи.
Отступление — не трусость, а способ сохранить форму без разрезов».
**Вердикт: РАСХОЖДЕНИЕ (атрибуция хода «ханэ»).**
Определение ханэ в уроке верно — Источник: https://senseis.xmp.net/?Hane
> "A hane is a move which 'reaches around' one or more of the opponent's
> stones."
Однако классический базовый дзёсэки для 3-4 с описанным итогом («чёрные
укрепляют угол, отступление сохраняет форму») — это цукэ-хики
(прижатие-отступление), где **ханэ играет атакующая сторона (белые), а не
чёрные**. Ответ чёрных на подход — контактный ход (цукэ), а не ханэ.
Источник: https://senseis.xmp.net/?34PointHighApproachAttachDrawbackJoseki
> "The 3-4 point high approach, attach-drawback joseki (Tsuke-Hiki) is one of
> the most popular 3-4 point joseki, and it one of the first joseki learned by
> beginners. Drawing back (hiki) is solid shape and secures the corner
> territory for Black."
Как должно быть: «белые подошли; чёрные прижимаются (цукэ), белые играют
ханэ, чёрные спокойно отступают (хики)». Порядок «ханэ → отступление»
существует, но ханэ принадлежит белым. Итоговая оценка позиции в уроке
(«чёрные укрепляют угол, белые получают внешнюю позицию») цукэ-хики
соответствует — эта часть верна. Дополнительно: в диаграмме урока чёрный
камень стоит непосредственно «над» белым (C2 над C3), что геометрически не
является ханэ (ханэ — изгиб по диагонали); диаграмма скорее изображает
прижатие.
### 8.4. Дзёсэки 3: вторжение 3-3 под хоси (4-4)
**Утверждение:** «Если соперник начал с 4-4 (хоси), угол ещё не закрыт —
вторгайтесь на 3-3. Дальше развивается типовая борьба: хозяин хоси давит с
двух сторон, вторгшийся ползёт по краю и живёт маленькой группой. Хозяин
строит внешнюю стену. Итог: земля против влияния, равенство».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?44PointJosekis
> "The 4-4 point (aka hoshi or 'star point') ... Compared with the 3-4 point
> or 3-3 point, the corner can still be invaded and the territory is not
> secure."
Источник: https://senseis.xmp.net/?44Point33InvasionJoseki
> "The 4-4 Point 3-3 invasion is a common and popular joseki ... it emphasizes
> territory at the expense of influence. The 3-3 point is the vital point of
> the corner, and it is easy for the invader to live underneath with a corner
> territory worth ~10 points."
Построение внешней стены хозяином хоси (традиционный вариант):
> "Historically, it was common and intuitive for Black to make a wall during
> the 3-3 invasion."
### 8.5. Дзёсэки обновляются; ИИ изменил оценки
**Утверждение:** «дзёсэки обновляются: профессионалы постоянно находят новые
варианты, а с появлением сильных программ многие оценки изменились».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Joseki
> "Which sequences are considered playable joseki has changed throughout
> history, owing to the effects of rule changes (most notably komi), continued
> research from professional players, and, most recently, from artificial
> intelligence analysis."
### 8.6. Поправка на доску 9×9
**Утверждение:** «на 9×9 влияние центра сильнее, чем на 19×19, и многие
классические дзёсэки там ведут себя иначе».
**Вердикт: ПОДТВЕРЖДЕНО (по совокупности; дословной формулировки «влияние
центра сильнее» в источнике нет).**
Источник: https://senseis.xmp.net/?9x9Openings (прочитана прямым
HTTP-запросом; web_open_url дал таймаут)
> "The 5-5 point seems to be the most popular opening." (на 9×9; на 19×19
> первый ход в тэнгэн/5-5 — редкость, что и показывает повышенную роль центра)
Источник: https://senseis.xmp.net/?9x9Strategy
> "You will note that this violates the principle which says Don't Attach When
> Attacking, marking the special nature of 9×9 Go."
---
## Урок 09. Форма
### 9.1. Тигровая пасть
**Утверждение:** «Тигровая пасть (кошачья пасть) — три камня вокруг пустого
пункта... если противник вклинится туда, вы ответите и соединитесь
окончательно. Пасть защищает разрез одним камнем».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?TigersMouth
> "This is the reference form of a tiger's mouth, that is formed by two
> diagonal moves in different directions. ... The tiger's mouth is most often
> used to make a connection known as a hanging connection."
Японское имя формы на той же странице — 猫の顔 (нэко но као, «кошачья
морда»), что оправдывает вариант «кошачья пасть».
Замечание к иллюстрации (внутренняя несогласованность, не источниковый факт):
в ASCII-диаграмме урока нарисованы только два камня (B1 и A2), а в тексте
описаны «три камня вокруг пустого пункта» — диаграмму следует дополнить
третьим камнем.
### 9.2. Бамбуковый стык: форма и неразрезаемость
**Утверждение:** «Бамбук... — два камня вплотную и ещё два через пункт...
разрезать бамбук почти невозможно, а тянется он быстро».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?BambooJoint
> "The name, like the 'knuckle' on a stick of bamboo, comes from the strength
> of the connection. It is normally impossible to cut through it."
> "Why is bamboo joint good? - It is hardly cuttable (but not impossible)"
Замечание к иллюстрации: в диаграмме урока показана только одна пара камней
(B B), вторая пара «через пункт» не нарисована — не соответствует ни
собственному тексту урока, ни эталонной форме.
### 9.3. Бамбуковый стык: японское название «танэки»
**Утверждение:** «Бамбук (танэки)».
**Вердикт: РАСХОЖДЕНИЕ.**
Источник: https://senseis.xmp.net/?BambooJoint (прочитана прямым
HTTP-запросом; web_open_url дал таймаут)
> "Japanese: タケフ (takefu), 二丁ツギ (nichou-tsugi)"
Как должно быть: японское название бамбукового стыка — «такэфу» (takefu).
Термина «танэки» в го-терминологии не существует (поиск «taneki» по
источникам результатов не дал). Вероятна опечатка/искажение.
### 9.4. Вытягивание (ноби)
**Утверждение:** «Вытягивание (ноби) — простой ход вперёд от своего камня.
Скромный, но самый надёжный способ добавить дамы и не создать слабостей».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Nobi
> "A play directly next to another stone of the same color ... The nobi
> advances your stones and creates more liberties for them. ... a nobi is more
> solid than alternatives to moving ahead ... since by definition it cannot be
> cut"
### 9.5. Пустой треугольник
**Утверждение:** «Пустой треугольник — три своих камня треугольником вокруг
пустого пункта, который ничего не защищает... Три камня делают работу двух...
Поговорка гласит: "пустой треугольник — не ставь никогда"».
**Вердикт: ПОДТВЕРЖДЕНО (с оговоркой источника).**
Источник: https://senseis.xmp.net/?EmptyTriangle
> "Most people like to say that the empty triangle is bad, being inefficient
> and prone to shortage of liberties."
Оговорка: тот же источник честно отмечает, что существуют ситуации, где
пустой треугольник — лучший ход ("good empty triangle"), поэтому абсолютная
формулировка поговорки — педагогическое упрощение, а не закон.
### 9.6. Столб (данго)
**Утверждение:** «Столб (данго) — камни, выстроенные в сплошную линию без
глаз и без выхода: тяжёлая масса, которую легко атаковать».
**Вердикт: РАСХОЖДЕНИЕ (описание формы и перевод).**
Источник: https://senseis.xmp.net/?Dango
> "Dango is a Japanese go term, meaning 'dumpling shape,' that refers to a
> solid mass of stones without eyes and few liberties — a very inefficient
> shape. ... This clump of four black stones is the smallest dango."
Как должно быть: данго — это «пельменная форма», то есть бесформенный
ком/куча камней (без глаз, с малым числом дамэ), а не «линия/столб». Суть
урока (тяжёлая, неэффективная, уязвимая масса без глаз) верна, но «сплошная
линия» — неточное описание: вытянутая линия камней сама по себе данго не
является, а перевод «столб» не соответствует значению термина («пельмень»).
### 9.7. Перегруппировка
**Утверждение:** «Перегруппировка — слишком много камней в малой области: они
мешают друг другу строить глаза и дают противнику лишние точки атаки».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Korigatachi
> "Overconcentrated shape means too many stones (of one colour) in one part of
> the board for the work they do; so shape that is inefficient because it's
> too solid."
---
## Урок 10. Простое ёсэ
### 10.1. Определение ёсэ
**Утверждение:** «Ёсэ — эндшпиль: стадия, когда большие территории уже
очерчены и игроки уточняют границы... Пропустить пару ходов ёсэ — значит
проиграть выигранную партию».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Yose
> "Yose ... are moves that approach fairly stable territory, typically
> enlarging one's own territory while reducing the opponent's. ... both in
> Japan and the West yose is at times used as a synonym for endgame."
### 10.2. Ценность ходов ёсэ «по одному-три очка»
**Утверждение:** «Ходы в ёсэ приносят по одному-три очка».
**Вердикт: НЕ НАЙДЕНО (для конкретного диапазона).**
Общий смысл («ходы ёсэ мелкие») подтверждается: Источник:
https://senseis.xmp.net/?Yose
> "Such plays are usually not as large as opening plays or middlegame plays
> affecting the life and death of large groups"
Однако конкретный диапазон «13 очка» ни один открытый источник дословно не
подтверждает (на https://senseis.xmp.net/?PracticalApproachToYose приведён
пример комбинации ханэ стоимостью 10 очков по дэири:
> "2 * (3 for second line hane + 2 for first line hane) = 10 points"),
> так что диапазон стоит считать неточным упрощением: крупные ёсэ-ходы бывают
> заметно дороже 3 очков.
### 10.3. Ханэ на первой линии как приём ёсэ
**Утверждение:** «Самый частый приём ёсэ — ханэ у края: ход, изгибающийся
вокруг камня противника на первой линии... Ханэ отодвигает границу на один
пункт в вашу пользу».
**Вердикт: техника ПОДТВЕРЖДЕНА; суперлатив «самый частый» — НЕ НАЙДЕНО.**
Источник: https://senseis.xmp.net/?HaneDescend
> "... is a hane-descend to the first line in the endgame."
Источник: https://senseis.xmp.net/?PracticalApproachToYose
> "Plays on the higher lines are larger. So play on the 3rd line first, 2nd
> line next, and 1st line last." (ханэ на первой/второй линии — типовые
> компоненты подсчёта ценности в ёсэ)
Утверждение о том, что ханэ первой линии — именно «самый частый» приём ёсэ,
источниками не подтверждено.
### 10.4. Сэнтэ
**Утверждение:** «Сэнтэ — ход, требующий ответа: после него инициатива
остаётся у вас».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Sente
> "A move is sente if the opponent has to answer it or suffer losses, so the
> player who plays it will have sente after the exchange."
### 10.5. Готэ
**Утверждение:** «Готэ — ход, на который можно не отвечать: инициатива
переходит сопернику».
**Вердикт: ПОДТВЕРЖДЕНО.**
Источник: https://senseis.xmp.net/?Gote
> "a move which loses the initiative, since it need not be answered by the
> opponent, thus giving him sente"
### 10.6. «Сначала сэнтэ, потом готэ»
**Утверждение:** «Правило ёсэ: сначала играйте сэнтэ-ходы, потом готэ.
Сэнтэ бесплатен... Готэ-ход "покупает" пункты ценой инициативы — берите его,
когда сэнтэ кончились».
**Вердикт: ПОДТВЕРЖДЕНО (как эвристика; источник добавляет оговорку).**
Источник: https://senseis.xmp.net/?Gote
> "non-gote moves are to be preferred to gote moves, playing a gote move is
> not necessarily a mistake. Informally, a gote move could be the best move if
> its value is more than twice that of any available sente sequence."
### 10.7. Оценка хода через разницу (свинг)
**Утверждение:** «Если ваш ход даёт +2 очка вам, а ход соперника там же дал
бы +2 ему, ценность пункта — 4 очка. Берите самые дорогие пункты первыми».
**Вердикт: ПОДТВЕРЖДЕНО (это дэири-подсчёт).**
Источник: https://senseis.xmp.net/?DeiriCounting
> "the deiri value is defined as the swing, i.e. the difference in the counts
> of the positions that result when Black or White plays first."
> "If Black plays first he scores four points ... If White plays first she
> scores two points ... The deiri value is the difference between these two
> results (the 'swing'), or 6 points."
> "Normally you should play the largest play"
### 10.8. Дамэ — нейтральные пункты
**Утверждение:** «Когда останутся только одноочковые размены (дамы —
нейтральные пункты), партия близка к концу».
**Вердикт: ПОДТВЕРЖДЕНО (терминология; см. уточнение).**
Источник: https://senseis.xmp.net/?Dame
> "In the endgame, empty points on the board which are not part of either
> player's territory and have no prospects of becoming territory. Normally,
> remaining neutral points are filled in at the end of the game by alternation
> between the players."
Уточнение: при японском подсчёте нейтральные дамэ вообще не приносят очков
(«одноочковые размены» верно лишь при подсчёте по площади); в уроке это
допустимое упрощение для начинающих.
---
## Сводная статистика
| Урок | Утверждений | ПОДТВЕРЖДЕНО | НЕ НАЙДЕНО | РАСХОЖДЕНИЕ |
| --------- | ----------- | ------------------------------------------------------ | ---------- | ----------- |
| 08 | 7 | 6 (одно — по совокупности, без дословной формулировки) | 0 | 1 |
| 09 | 7 | 5 | 0 | 2 |
| 10 | 9 | 7 | 2 | 0 |
| **Итого** | **23** | **18** | **2** | **3** |
Дополнительно (не входят в статистику, т.к. это внутренние
несогласованности иллюстраций, а не источниковые факты):
- урок 09, «тигровая пасть»: в диаграмме 2 камня вместо описанных трёх;
- урок 09, «бамбук»: в диаграмме показана одна пара камней вместо двух пар.

View file

@ -0,0 +1,89 @@
# ADR-0001. Триаж prestart-litany для профиля M
- Дата: 2026-08-05
- Статус: принят
- Ссылки на литанию — слагами/названиями пунктов, не номерами (`gotcha-renumbered-parent`).
## Контекст
Проект «Сайт обучения Го»: профиль **M** (продукт без своего прода) — код, тесты,
релизы, но нет своего сервера/БД (до опциональных этапов 89). Полностью клиентский:
статическая сборка на шаред-хостинг, движок и ИИ — в браузере. Один разработчик,
сессии ведут разные ИИ-модели без общей памяти.
## Триаж
| Пункт литании | Вердикт | Причина / условие |
| ---------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0.2 Минимальное ядро (все 9 пунктов) | IN | Действует всегда, для любого профиля |
| 0.3-D/S/A профильные добавки | OUT | Не наш профиль |
| 0.3-M профильные добавки | IN | Чистое ядро и детерминизм (II), версионирование локального состояния (I.14), адаптер платформы, CI-матрица ОС (II.11), профиль публичного релиза (V.9), роли (IV) |
| 0.3-XL литания целиком | DEFERRED | Активация: старт этапа 8 (PHP+SQLite, свой прод) — до-триаж раздела V обязателен по спеке |
| I.1 Модульность / Module Map | IN | packages/* = модули; Module Map в AGENTS.md |
| I.2 Сервисный слой | IN (адаптация) | Без backend: контроллер→сервис читается как UI→чистые функции ядра; логика только в packages/core и packages/ai |
| I.3 Статусы = конечный автомат | IN | Состояние партии/урока — именованные переходы, прямое присваивание запрещено |
| I.4 Аудит-след | OUT | Нет транзакций, денег, сервера. Журнал ходов партии существует как доменная сущность (история с ветвлением), не как аудит |
| I.5 Soft delete | OUT | Нет БД и документов |
| I.6 Числа/единицы/стандартные форматы | IN | Координаты доски, коми (0.5-шаг), размер доски — объявлены письменно в INTERFACES.md; SGF FF[4] — стандартный формат обмена, берём его |
| I.7 Миграции схемы append-only | IN (адаптация) | Схема localStorage: version + миграция при загрузке; БД-миграции — DEFERRED до этапа 8 |
| I.8 Внешние вызовы | OUT | Нет внешних сервисов на этапах 17. Загрузка весов KataGo (этап 9) — статический файл, не API; адаптер площадки не нужен (свой сайт, не маркетплейс) |
| I.9 Парные операции симметричны | IN | Сделать ход ↔ отмена хода; поставить разметку ↔ снять; тесты на пары |
| I.10 Без хардкода / доменные параметры | IN | Коми, бюджеты времени ИИ, вероятности уровней — константы/конфиг одним местом (packages/core/src/config или данные уроков) |
| I.11 Contract First | IN | INTERFACES.md — контракт движка, протокол postMessage воркера, форматы уроков, схема localStorage. Сначала запись, потом код; удалил сущность — докажи grep'ом |
| I.12 Docker-first | OUT | Статика на шаред-хостинге, контейнеров нет |
| I.13 Fail-safe | IN | Битый сейв → старт с дефолтов с пометкой; нет SharedArrayBuffer → честный однопоточный фолбэк; фича-флаги (этапы 89) по умолчанию выключены |
| I.14 Персистентное состояние вне БД | IN | localStorage: version + checksum + атомарность (насколько позволяет API) + миграция + roundtrip-тест — прямо в спеке |
| I.15 Права на уровне выборки | OUT | Нет сервера и пользователей |
| I.16 Retention / классы данных для AI | IN (частично) | Сборов данных нет по спеке; класс «данные пользователя не покидают браузер» объявлен письменно здесь: прогресс и партии — только localStorage, во внешние сервисы (включая облачные AI) не отправляются |
| I.17 Один назначенный писатель | IN | Состояние партии пишет только UI-поток; воркер ИИ — чистый вычислитель, обмен через postMessage |
| I.18 Не создавай второй канал доставки | IN | Вся статика (включая веса сети на этапе 9) отдаётся тем же хостингом, что и код |
| I.19 Файлы | IN (адаптация) | SGF-файлы — только через File API в браузере, без сервера |
| I.20 Автономный/демо-режим | IN | Детерминированные реплеи и тестовые партии — обычные входные данные движка, не ветки if |
| II.1 Гейты с первого коммита | IN | prettier + eslint + tsc -b --noEmit + vitest = `npm run gates`; hard-блокирующие. Поведение при провале: стоп, показать гейт и stderr, не чинить молча |
| II.2 Правило→хук | OUT | Проект ведётся разными инструментами/моделями, PreToolUse-хуки — фича одного инструмента; машинная проверка — через npm-гейты и CI (`gotcha-two-agent-configs`) |
| II.3 Проверяй каждый слой сразу | IN | Прогон gates после каждого слоя каркаса и каждого модуля |
| II.4 Тулчейн проверяй фактически | IN | Выполнено при развёртывании: версии сверены через npm view 2026-08-05 и запинены |
| II.5 Типы обязательны, функции ≤40 строк | IN | strict без any; превышение 40 строк — с явным обоснованием в комментарии |
| II.6 Тесты зеркалят структуру, багфикс приносит тест | IN | AAA; тест = полная жизненная цепочка; для ядра — инварианты алгоритма в docstring + property-based тесты (fast-check, добавляется на этапе 1) |
| II.7 Тесты wiring — отдельный класс | IN | Orphan-check по каждой публичной функции ядра + интеграционный тест полной партии — прямо в спеке этапа 1 (`gotcha-green-modules-dead-system`) |
| II.8 Тест без боевой среды — спроектирован | IN | Ядро не импортирует DOM и воркер-API (две оси изоляции); Date.now()/Math.random() запрещены в ядре, часы и seeded RNG инжектируются; headless-прогон партий ботом с проверкой метрик |
| II.9 Фоновые задачи/очередь | OUT | Нет очереди; Web Worker — не таска в смысле пункта, протокол фиксируется в INTERFACES.md |
| II.10 Фронтенд | IN (адаптация) | strict без any; без react-query/zustand/RHF — UI минимальный, состояние своё; строки UI — в один ресурсный файл с первого дня; error boundary на маршрут; бюджеты числом (страница урока ≤ 300 КБ) |
| II.11 CI — не только гейты | IN | GitHub Actions: gates + матрица ubuntu+windows + гейт «чистый клон собирается» + npm audit. Настраивается на этапе 1 вместе с первым кодом |
| II.12 Лицензионный гейт | IN | Только пермиссивные лицензии зависимостей; THIRD_PARTY_LICENSES.md; цумэ-го — свободная лицензия с атрибуцией (CREDITS.md); лицензия весов KataGo проверяется на этапе 9 |
| III.1 Карта навигации | IN | Module Map в AGENTS.md + docs/navigation.md (появится с кодом); крупные файлы — порог + команда генерации |
| III.2 Один индекс и один гейт документации | IN (адаптация) | Доки — markdown в docs/, индекс — docs/README.md; гейт ссылок — легковесный скрипт проверки относительных ссылок (в gates), без mkdocs |
| III.3 ADR | IN | Этот файл — первый. Существующие ADR не редактируются |
| III.4 Ритуал закрытия сессии | IN | Триггер «закрываем сессию»; если сводить нечего — сказать явно |
| III.5 Статусы в планах по факту кода | IN | Реестр «Критические долги» в начале roadmap; закрытие долга — двухфазно |
| III.6 Версии | IN | SemVer; SoT версии — корневой package.json; версия наблюдаема в подвале сайта; бамп только по явной команде владельца; push тега — отдельное «да» |
| IV.1 Модель ветвления | IN | Trunk-based: один разработчик, ревью — сам сверка с гейтами; фиксируется здесь |
| IV.2 Делегирование субагентам | IN | Правку 13 строк не делегировать; субагентам — правило контекст-экономии и запрет на commit/push |
| IV.3 Опасные команды письменно | IN | Список в AGENTS.md; одно «да» = одно действие; escape-вектора (cd && git push, sh -c, eval, xargs) не использовать |
| IV.4 Не переспрашивай и не угадывай | IN | Дублируется в мастер-промпте |
| IV.5 TodoWrite, планы в docs/plans/ | IN | Двухфазность: фикс отдельным коммитом, доки отдельным |
| IV.6 Бюджет точки входа агента | IN | Точка входа — AGENTS.md (инструменто-нейтральная), только то, что нужно каждую сессию; чужие конфиг-каталоги не заводить |
| IV.7 Нарушаемое правило — дефект системы | IN | Заморозки и осознанные исключения — письменно в самом правиле |
| IV.8 Гигиена сессии | IN | Ссылки на docs/ в ответах; конфиги сторонних сервисов — по актуальной доке (`gotcha-llm-stale-configs`) |
| IV.9 Протокол отладки | IN | «Снаружи внутрь»: UI → воркер → движок; зафиксирован в мастер-промпте |
| V.1V.8 Прод/эксплуатация/бэкапы | DEFERRED | Активация: этап 8 (свой backend на шареде). До него прода нет — деплой = FTP-заливка статики |
| V.9 Профиль публичного релиза | IN | Сайт публичный: LICENSE, README-витрина со скриншотами (с этапа 7), CREDITS.md, внутренние доки на русском — записано здесь |
| V.10 Межпроектные контракты | OUT | Потребителей значений проекта вне его нет |
| VI Грабли: [Docker] группа | OUT | Контейнеров нет |
| VI Грабли: [инфра/прод] группа | DEFERRED | Активация с этапом 8. Исключения, уже в спеке этапа 7: `gotcha-https-redirect-proxy` (IN — редирект средствами панели), `gotcha-robots-secret` (IN — актуально при открытии индексации) |
| VI Грабли: данные и логика, версии, Windows, процесс | IN | Все применимые — постоянный чеклист; особо: `gotcha-gitattributes` (закрыт первым коммитом), `gotcha-non-ascii-toolchain` (файлы для тулчейна — ASCII), `gotcha-green-modules-dead-system` и `gotcha-orphan-config` (wiring-тесты этапа 1), `gotcha-docs-drift` (ритуал закрытия), `gotcha-phantom-tails` (сверка плана с кодом в начале сессии), `gotcha-modal-drag-close` (этап 5, UI) |
## Компромиссы — что осознанно потеряли
1. **Нет машинных хуков (II.2 OUT)** — дисциплина опирается на npm-гейты и CI, а не на
блокирующие хуки инструмента. Цена: правило можно нарушить в спешке и узнать об этом
только на прогоне gates. Принято, потому что проект ведётся разными моделями и
инструментами (`gotcha-two-agent-configs`, `gotcha-hook-shell-blind`).
2. **Нет Docker-воспроизводимости среды сборки** — воспроизводимость обеспечивается
запиненными версиями + engines.node + CI-гейтом «чистый клон собирается».
3. **Гейт документации — самодельный скрипт ссылок, не mkdocs --strict** — проще, но
ловит только битые относительные ссылки, не полноту nav.
4. **Trunk-based без внешнего ревью** — роль ревью выполняют гейты + ритуал закрытия +
сверка владельца; ошибка может дойти до main, откат — реверт-коммитом.
5. **Этапы 89 остаются недо-триаженными** — раздел V литании активируется отдельным
до-триажем при старте этапа 8; до тех пор правила прода не действуют.

View file

@ -0,0 +1,57 @@
# ADR-0002: алгоритм поиска групп и дамэ — flood fill
Дата: 2026-08-05 | Статус: принято (этап 1)
## Контекст
Движку правил (packages/core) на каждый ход нужно находить группы камней и их
дамэ: при проверке захватов, самоубийства, при подсчёте и в эвристике мёртвых
групп. План этапа 1 предписывает выбрать между flood fill и union-find по
микро-бенчмарку и зафиксировать выбор в ADR.
## Альтернативы
1. **Flood fill** — обход группы стеком от стартового камня, дамэ собираются в
множество. Простая реализация, локальный обход (только затронутые группы).
2. **Union-find** — массив parent на всю доску, объединение соседей одного
цвета, дамэ агрегируются по корням. Теоретически эффективен при
инкрементальных обновлениях, но наша позиция неизменяема: структуру пришлось
бы перестраивать на каждый ход, что обнуляет его главное преимущество.
## Бенчмарк
`packages/core/bench/group-bench.mts` (запуск: `npx tsx ...`): оба алгоритма
находят все группы и дамэ на случайных позициях 19×19 (плотности 0.35/0.55/0.75,
40 позиций × 300 итераций на плотность, детерминированный RNG). Среда:
Node.js 20, x86-64. Контрольные суммы результатов совпали.
| Плотность | flood fill | union-find | u/f |
| --------- | ---------- | ---------- | ---- |
| 0.35 | 230.4 мс | 373.1 мс | 1.62 |
| 0.55 | 288.5 мс | 505.6 мс | 1.75 |
| 0.75 | 328.3 мс | 596.8 мс | 1.82 |
| Итого | 847.1 мс | 1475.5 мс | 1.74 |
Union-find проигрывает на всех плотностях (в 1.61.8 раза): на маленькой доске
(361 клетка) стоимость полного прохода union-find и аллокаций Map/Set по корням
не окупается, а flood fill обходится дешёвыми операциями над массивом и одним
множеством дамэ на группу.
## Решение
Оставляем **flood fill** (реализован в `groupAt`, packages/core/src/board.ts).
## Причины
- Быстрее на целевом профиле (доски 919, обходы только затронутых групп).
- Существенно проще код и меньше аллокаций; неизменяемость BoardState всё равно
не даёт переиспользовать структуру union-find между ходами.
- Производительности достаточно с запасом: полный пересчёт всех групп 19×19 —
десятки микросекунд, а движок обходит лишь соседние с ходом группы.
## Компромисс
Если этапы 34 (ИИ) покажут профилировкой, что пересчёт групп — узкое место
(например, массовые симуляции MCTS), решение пересматривается: кандидат —
инкрементальный union-find с копированием при записи или кэш групп в узлах
симуляции. Точка пересмотра — бенчмарк на реальной нагрузке ИИ, не раньше.

View file

@ -0,0 +1,35 @@
# ADR-0003: buildPosition в core + разметка цели CR вместо MA (этап 6)
Дата: 2026-08-06. Статус: принято.
## Контекст
Этап 6 (уроки и цумэ-го) требует стартовать задачу с произвольной позиции.
План-фаза 6 фиксировала «packages/* не трогаем», но аудит выявил два
пробела контракта:
1. parseSgf (этап 1) не поддерживает setup-свойства AB/AW — произвольную
позицию из SGF не собрать, а конструировать BoardState вручную вне core
опасно (positionHashes для суперко считает внутренний hashPosition).
2. NodeMarkup не содержит стрелок MA (только LB/TR/SQ/CR) — разметка цели
«connect» по контракту была невыразима.
## Решение
1. В packages/core добавлена ОДНА аддитивная функция
`buildPosition(size, black, white, toPlay)` (board.ts, экспорт через
index.ts): валидация (вне доски / дубли / группа без дамэ → Error),
корректный positionHashes. Поведение существующих функций не меняется,
зона core в остальном заморожена.
2. Разметка цели connect в задачах — CR (круги) вместо MA; контракт
INTERFACES.md обновлён. SGF задачи хранит позицию данными JSON
(setupBlack/setupWhite), а SGF-поле несёт только дерево вариантов
(B/W-ходы от стартовой позиции, TR/CR-разметку цели на корневом узле).
## Последствия
- Позиция задачи: JSON-поля setupBlack/setupWhite + buildPosition; дерево
вариантов парсится parseSgf и накатывается applyMove на построенную
позицию.
- При расширении parseSgf поддержкой AB/AW в будущем формат можно
упростить до чистого SGF (миграция контента, отдельной задачей).

View file

@ -0,0 +1,43 @@
# ADR-0004: личный кабинет email+пароль (этап 8) и до-триаж раздела V
Дата: 2026-08-06. Статус: принято (решение владельца).
## Контекст
Владелец заказал «маленький личный кабинет» с хранением прогресса —
активация этапа 8 (ОПЦИОНАЛЬНЫЙ по спеке: «только по отдельной команде»).
Спека этапа 8 предписывала анонимный токен; владелец выбрал
**email + пароль** (ask_user 2026-08-06) — осознанное отступление от
«без сбора данных»: email — персональные данные, храним минимум
(email + хэш пароля), никакой аналитики/трекинга не добавляется.
## Решение
1. Регистрация email+пароль, password_hash/password_verify, сессии
PHP native (HttpOnly, SameSite=Lax). Контракт API — INTERFACES.md.
2. **Без верификации почты и восстановления пароля**: на shared-хостинге
нет гарантии SMTP; потерянный пароль = новая учётка. Это предупреждение
показывается в UI при регистрации (зафиксировано в контракте).
3. Прогресс — opaque-блоб ProgressV2 на сервере, last-write-wins по
updatedAt; localStorage остаётся SoT для неавторизованных.
## До-триаж раздела V литании (требование мастер-промпта для этапа 8)
| Пункт | Вердикт | Комментарий |
| --------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| V.1 (агент не трогает прод) | IN | Прод-деплой — только команды владельцу блоком; агент не выполняет |
| V.2 (деплой-чеклист) | IN | docs/deploy.md: ВСЕ артефакты (dist → web root, server/ → /api, config.php, БД вне web root), smoke = health.php, откат = перезаливка предыдущего dist |
| V.3 (один скрипт деплоя) | OUT | Нет SSH/контейнеров, деплой по FTP; чек-лист V.2 покрывает |
| V.4 (секрет-гигиена) | IN | config.php с путём БД — вне репо (.gitignore), в репо config.example.php с плейсхолдерами [SECRET]; секретов в коде нет |
| V.5 (канонические пути) | IN | Пути на хостинге фиксируются в docs/deploy.md письменно при первом деплое (владельцем) |
| V.6 (наблюдаемость) | IN (минимум) | health.php {"status":"ok"}; внешний наблюдатель/алерты — OUT (нет сервера в нашем управлении) |
| V.7 (бэкап) | IN (минимум) | Бэкап = копия SQLite-файла средствами панели хостинга; процедура restore в deploy.md; автоматика — OUT |
| V.8 (приёмка/ворота) | IN | Приёмка этапа 8 = smoke.sh зелёный локально + ручной прогон на хостинге владельцем |
## Последствия
- В превью (статический хостинг) API недоступен — UI кабинета обязан
деградировать тихо («синхронизация недоступна»), без алертов.
- CI-гейты не запускают PHP (в среде его нет у GitHub runner по
умолчанию — есть: shivammathur/setup-php при желании; в v1 smoke.sh
локальный/на хостинге, зафиксировано в PROJECT_STATE).

101
docs/deploy.md Normal file
View file

@ -0,0 +1,101 @@
# Деплой на shared-хостинг (чек-лист, V.2)
Дата: 2026-08-06. Выполняет ВЛАДЕЛЕЦ руками (V.1: агент прод не трогает).
Проходить целиком, частичные варианты запрещены (gotcha-partial-build).
## Артефакты деплоя (ВСЕ, их ТРИ)
1. **Статика сайта**: `apps/web/dist/` → web root хостинга (например,
`public_html/`). Сборка: `npm ci && npm run build` в `apps/web`
(Node 20+, локально) либо взять готовый dist из превью-версии.
2. **API**: содержимое `server/` раскладывается по web root ЧАСТЯМИ
(не целиком!): `server/api/``public_html/api/`,
`server/lib/``public_html/lib/`. Эндпоинты оказываются доступны
как `/api/*.php`. Пути жёсткие: PHP-файлы ищут lib как `../lib/`
(smoke-тест локально поднимает docroot = server/, поэтому на проде
web root = dist + содержимое server/).
3. **Конфиг + БД**: скопировать `server/config.example.php`
`public_html/config.php` (именно в КОРЕНЬ web root — db.php ищет его
как `__DIR__.'/../config.php'` от lib/), выставить `db_path` — путь
ВНЕ web root (например, `/home/<user>/go-learn.sqlite`). Сам файл БД
создастся сам при первом обращении (права на каталог — записываемые
для PHP). Также выставить `base_url` (полный URL сайта — идёт в письма
восстановления пароля) и `mail_from` (отправитель писем).
## Итоговая структура на хостинге
```text
public_html/
├── index.html, learn.html, play.html, … ← из dist (артефакт 1)
├── _astro/, assets/ ← из dist
├── .htaccess ← из dist + блок запрета ниже
├── api/ ← server/api/ (артефакт 2)
│ ├── health.php, register.php, login.php, logout.php, me.php,
│ ├── progress.php, captcha.php
│ └── password-reset/ (request.php, confirm.php)
├── lib/ ← server/lib/
│ └── auth.php, db.php, http.php, ratelimit.php
└── config.php ← созданный вручную (артефакт 3)
```
В корневой `.htaccess` (из dist) дописать блок из server/.htaccess:
```apache
<IfModule mod_authz_core.c>
<FilesMatch "(^config\.php$|\.sqlite(-wal|-shm)?$)">
Require all denied
</FilesMatch>
</IfModule>
```
## Порядок (каждый шаг — отдельное действие)
0. Проверки хостинга: PHP ≥ 8.0, модули pdo_sqlite, sqlite3
(phpinfo в панели или `php -v`/`php -m` по SSH, если есть).
Нет pdo_sqlite — стоп, вопрос хостеру.
1. Залить dist (артефакт 1).
2. Залить server/ (артефакт 2) и создать config.php (артефакт 3).
3. Smoke: открыть `/api/health.php` → ожидается `{"status":"ok"}`.
Не 200/не JSON — смотреть логи PHP в панели, НЕ править код на глазах.
4. Smoke вручную: регистрация на странице /register.html (с капчей)
→ вход → «Синхронизировать прогресс» → статус «Прогресс отправлен».
4.1. Проверка почты (этап 8.1): «Забыли пароль?» на /account.html →
письмо со ссылкой приходит и ссылка ведёт на
/password-reset.html?token=…. Не приходит — смотреть настройки
SPF/DKIM в панели хостинга; до решения восстановление считать
нерабочим (риск в PROJECT_STATE).
5. Проверить, что БД-файл появился по пути из config.php и НЕ доступен
по HTTP (попытка открыть его URL → 403/404).
6. HTTPS — включать ТОЛЬКО панелью хостинга (в .htaccess редиректа нет
намеренно, gotcha-https-redirect-proxy). После включения — повторить
шаги 34 по HTTPS.
## Канонические пути (V.5) — заполнить при первом деплое
- Web root: `____________`
- URL сайта: `____________`
- Путь БД (вне web root): `____________`
- Дата первого деплоя: `____________`
## Откат
Откат = перезаливка предыдущего dist и (при изменении server/) предыдущего
server/. Схема БД этапа 8 аддитивна (init-only), обратной миграции нет;
перед любым изменением схемы в будущем — дамп БД ДО деплоя (V.3).
## Бэкап и restore (V.7, минимум)
- Бэкап = копия файла SQLite (из панели файлового менеджера/SSH) +
config.php. Периодичность — после заметного роста числа пользователей
(ручная, SoT расписания — этот файл).
- Restore: остановить запись (выключить сайт в панели) → вернуть файл БД
на путь из config.php → smoke шаг 34. Проверка restore выполняется на
первом же бэкапе: распаковать копию под другим именем и открыть
`sqlite3 <файл> "SELECT count(*) FROM users;"` (или через панель).
## Чего НЕ делать
- Не класть config.php и БД в web root.
- Не включать HTTPS-редирект в .htaccess (только панель).
- Не править PHP/БД на проде напрямую: правка → локальный smoke.sh →
перезаливка.

View file

@ -0,0 +1,116 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Кабинет — Иго · школа Го</title>
<meta
name="description"
content="Вход в кабинет школы Го: синхронизация прогресса между устройствами."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html" aria-current="page">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="container">
<!-- состояние: гость -->
<div class="card auth-card" data-logged-out>
<h1>Вход в кабинет</h1>
<p style="text-align: center; color: var(--ink-soft); font-size: 0.95rem">
Кабинет нужен только для синхронизации прогресса между устройствами. Без него всё
работает так же.
</p>
<form data-login-form novalidate>
<div class="field">
<label for="email">Электронная почта</label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
placeholder="you@example.com"
/>
<span class="error-text">Введите корректный адрес почты.</span>
</div>
<div class="field">
<label for="password">Пароль</label>
<input
type="password"
id="password"
name="password"
autocomplete="current-password"
placeholder="••••••••"
/>
<span class="error-text">Пароль не может быть пустым.</span>
</div>
<button class="btn btn--primary" type="submit" style="width: 100%">Войти</button>
</form>
<div class="auth-links">
<span>Нет аккаунта? <a href="register.html">Зарегистрируйтесь</a></span>
<a href="register.html">Забыли пароль?</a>
</div>
</div>
<!-- состояние: вы вошли -->
<div class="card auth-card logged-in" data-logged-in hidden>
<h1>Вы вошли</h1>
<p class="email" data-user-email>you@example.com</p>
<p style="color: var(--ink-soft); font-size: 0.95rem">
Прогресс пока на этом устройстве. Нажмите синхронизацию, чтобы забрать его с собой.
</p>
<div class="btn-row" style="justify-content: center">
<button class="btn btn--primary" type="button" data-sync>
Синхронизировать прогресс
</button>
<button class="btn btn--danger" type="button" data-logout>Выйти</button>
</div>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,166 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Иго — научитесь играть в Го с нуля</title>
<meta
name="description"
content="Камерная школа Го (вэйци, бадук) на русском: курс с нуля до 10 кю, цумэ-го с проверкой, партии против ИИ шести уровней."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<div>
<span class="hero-kicker">вэйци · бадук · го</span>
<h1>Научитесь играть <em>в&nbsp;Го</em> с&nbsp;нуля</h1>
<p class="lead">
Спокойный курс без регистрации-принуждения: теория, задачи с проверкой на доске и
партии против компьютера — от первых камней до уверенного 10&nbsp;кю.
</p>
<div class="btn-row">
<a class="btn btn--primary" href="learn.html">Начать курс</a>
<a class="btn" href="play.html">Сыграть с ИИ</a>
</div>
<div class="hero-meta">
<div class="item"><strong>10</strong><span>разделов курса</span></div>
<div class="item"><strong>12</strong><span>задач цумэ-го</span></div>
<div class="item"><strong>6</strong><span>уровней ИИ</span></div>
</div>
</div>
<figure class="hero-figure">
<span class="hero-chip"><span class="dot"></span> Ваш ход — E5</span>
<img
src="assets/hero.jpg"
alt="Деревянный гобан с несколькими чёрными и белыми камнями в начальной позиции"
width="1024"
height="576"
/>
<figcaption>Гобан, сланец и ракушка — три тысячи лет той же партии.</figcaption>
</figure>
</div>
</section>
<section class="features">
<div class="container">
<div class="section-head">
<div>
<p class="kicker">как это работает</p>
<h2>Как устроено обучение</h2>
</div>
<p>
Три простые вещи, которые вместе дают рост: теория малыми дозами, задачи до
автоматизма и живая практика.
</p>
</div>
<div class="features-grid">
<div class="card feature-card">
<span class="icon"></span>
<h3>С нуля, по шагам</h3>
<p>
Десять разделов: от дамэ и атари до дзёсэки и ёсэ. Короткие уроки, схемы позиций,
никакой воды — только то, что работает на доске.
</p>
</div>
<div class="card feature-card">
<span class="icon"></span>
<h3>Задачи с проверкой</h3>
<p>
Жизнь и смерть, сэмэаи, лестницы. Фильтры по теме и сложности, решённые отмечаются
автоматически — видно, что уже в опоре.
</p>
</div>
<div class="card feature-card">
<span class="icon"></span>
<h3>Партии с ИИ, 6 уровней</h3>
<p>
Доски 9×9, 13×13 и 19×19, коми, отмена хода и подсказки трёх лучших ходов. Начните с
первого уровня — он терпелив.
</p>
</div>
</div>
<div class="ink-band">
<figure>
<img
src="assets/ink.jpg"
alt="Позиция на углу гобана в манере тушевой живописи суми-э"
loading="lazy"
/>
<figcaption>
Угол доски, тушь и пустое пространство — эстетика ма, в которой учится глаз.
</figcaption>
</figure>
</div>
<div class="home-strip">
<figure class="hero-figure">
<img
src="assets/stones.jpg"
alt="Чёрные сланцевые и белые ракушечные камни Го в деревянных чашах"
loading="lazy"
/>
</figure>
<div class="card quote">
<blockquote>
«Го — это игра, которую легко выучить и невозможно исчерпать».
</blockquote>
<cite>— старая пословица. Проверьте на себе: первый урок занимает десять минут.</cite>
<p style="margin-top: 24px">
<a class="btn btn--quiet" href="lesson.html">Первый урок: дамэ и захват →</a>
</p>
</div>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,193 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Курс — Иго · школа Го</title>
<meta
name="description"
content="Программа курса Го с нуля до 10 кю: десять разделов от дамэ и захвата до простого ёсэ."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html" aria-current="page">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="page-head">
<div class="container">
<p class="kicker">с нуля до 10 кю</p>
<h1>Программа курса</h1>
<p>
Десять разделов, каждый — короткая теория и задачи на доске. Идите по порядку: каждый
раздел опирается на предыдущий. Отметки «пройден» сохраняются на вашем устройстве.
</p>
</div>
</div>
<div class="container">
<div class="course-grid">
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="dame">
<span class="lesson-num">1</span>
<div>
<h3>Дамэ и захват</h3>
<p>
Дыхания группы, постановка камня на доску, как камни снимаются с доски. Самый первый
урок — с него начинается всё.
</p>
<div class="meta"><span class="badge">10 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="atari">
<span class="lesson-num">2</span>
<div>
<h3>Атари, лестница, сеть</h3>
<p>
Угроза захвата и три способа её реализовать: прямое атари, ситэтя (лестница) и гэтэ
(сеть).
</p>
<div class="meta"><span class="badge">15 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="ko">
<span class="lesson-num">3</span>
<div>
<h3>Самоубийство и ко</h3>
<p>
Запрещённые пункты, правило ко и ко-угрозы. Почему партия не может повторяться
бесконечно.
</p>
<div class="meta"><span class="badge">12 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="eyes">
<span class="lesson-num">4</span>
<div>
<h3>Глаза: жизнь и смерть</h3>
<p>
Два глаза — жизнь. Настоящие и ложные глаза, базовые формы: прямая четвёрка, гнутая
тройка, косяк.
</p>
<div class="meta"><span class="badge">20 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="scoring">
<span class="lesson-num">5</span>
<div>
<h3>Подсчёт и конец партии</h3>
<p>
Территория и пленники, коми, два паса, мёртвые камни. Как понять, кто победил, не
считая на пальцах.
</p>
<div class="meta"><span class="badge">15 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="connect">
<span class="lesson-num">6</span>
<div>
<h3>Связь и разрезание</h3>
<p>Крепкие и тонкие связи, точки разрезания, защита своих слабостей и атака чужих.</p>
<div class="meta"><span class="badge">15 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="corners">
<span class="lesson-num">7</span>
<div>
<h3>Углы, стороны, центр</h3>
<p>
Почему партия начинается в углах, ценность третьей и четвёртой линии, первые
принципы фусэки.
</p>
<div class="meta"><span class="badge">15 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="joseki">
<span class="lesson-num">8</span>
<div>
<h3>Базовые дзёсэки</h3>
<p>
Установленные последовательности в углу: хоси-кэима, комоку и три простейших дзёсэки
наизусть.
</p>
<div class="meta"><span class="badge">25 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="shape">
<span class="lesson-num">9</span>
<div>
<h3>Форма</h3>
<p>
Хорошие и плохие формы: тигровая пасть, бамбуковый стык, пустой треугольник и почему
его избегают.
</p>
<div class="meta"><span class="badge">20 мин</span><span data-done-slot></span></div>
</div>
</a>
<a class="card card--link lesson-card" href="lesson.html" data-lesson-card="yose">
<span class="lesson-num">10</span>
<div>
<h3>Простое ёсэ</h3>
<p>
Эндшпиль без боли: сэнтэ и готэ, ценность ходов, типичные завершения на границе
территорий.
</p>
<div class="meta"><span class="badge">20 мин</span><span data-done-slot></span></div>
</div>
</a>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,184 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Урок 1. Дамэ и захват — Иго · школа Го</title>
<meta
name="description"
content="Первый урок курса Го: дыхания группы, дамэ, постановка и снятие камней с доски."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html" aria-current="page">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="page-head">
<div class="container container--narrow">
<p class="kicker">урок 1 из 10</p>
<h1>Дамэ и захват</h1>
<p>
Десять минут — и вы будете знать главное правило Го. Всё остальное в игре растёт из
него.
</p>
</div>
</div>
<div class="container">
<article class="lesson-body">
<h2>Камень живёт, пока дышит</h2>
<p>
Камни ставятся на <strong>пересечения линий</strong>, а не в клетки. Поставленный камень
не двигается — он либо остаётся на доске до конца партии, либо снимается при захвате.
</p>
<p>
Соседние пункты по линиям — это <strong>дамэ</strong>, дыхания камня. У камня в центре
четыре дыхания, на стороне — три, в углу — два.
</p>
<pre>
· ─ · ─ ·
│ │ │
· ─ ○ ─ · у белого камня
│ │ │ четыре дамэ: слева, справа,
· ─ · ─ · сверху и снизу</pre>
<p class="diagram-caption">Диаграмма 1. Четыре дыхания камня в центре доски.</p>
<p>
Камни одного цвета, стоящие рядом по линиям, образуют <strong>группу</strong> — они
делят дыхания и живут или умирают вместе.
</p>
<h2>Атари и захват</h2>
<p>
Когда у группы остаётся последнее дыхание, говорят <strong>«атари»</strong>
предупреждение об угрозе. Если противник занимает последнее дамэ, группа
<strong>снимается с доски</strong> и становится пленниками.
</p>
<pre>
· ─ ● ─ ·
│ │ │
● ─ ○ ─ · белый в атари: осталось
│ │ │ одно дамэ справа
· ─ ● ─ ·</pre>
<p class="diagram-caption">Диаграмма 2. Чёрные поставили белого в атари.</p>
<pre>
· ─ ● ─ ·
│ │ │
● ─ ● ← · чёрные заняли последнее дамэ —
│ │ │ белый камень снят с доски
· ─ ● ─ ·</pre>
<p class="diagram-caption">Диаграмма 3. Захват: пленник уходит в чашу чёрных.</p>
<div class="try-it">
<div
class="goban-static"
data-size="9"
data-black="d4,c5,e5,d6"
data-white="d5"
data-mark="e4"
></div>
<div>
<p class="label">Попробуйте сами</p>
<p>
Белый камень на D5 в атари. Куда чёрным поставить камень, чтобы снять его с доски?
Отметка подсказывает район — найдите точное пересечение, а затем
<a href="play.html">проверьте себя в игре против ИИ</a>.
</p>
</div>
</div>
<h2>Зачем это знать</h2>
<p>
Вся тактика Го — игра с дыханиями: вы сокращаете дамэ чужих групп и бережёте свои.
Лестница, сеть, сэмэаи, жизнь и смерть — всё это про дыхания, которые мы разберём в
следующих уроках.
</p>
<p>
Запомните три числа: <strong>432</strong>. Столько дыханий у одинокого камня в центре,
на стороне и в углу. Поэтому угол и умирает быстрее всех — и поэтому же там проще всего
строить территорию, но об этом в седьмом уроке.
</p>
<div class="try-it">
<div
class="goban-static"
data-size="9"
data-black="b2,c2"
data-white="b3,c3,a2"
data-last="a2"
></div>
<div>
<p class="label">Попробуйте сами</p>
<p>
Два белых камня стоят группой. Сколько дамэ у этой группы? Посчитайте все пустые
соседние пункты обоих камней — общие дыхания считаются один раз.
</p>
</div>
</div>
<h2>Кратко</h2>
<p>
Камни ставятся на пересечения и не двигаются. Дыхания (дамэ) — соседние пустые пункты.
Последнее дамэ занято — группа снята. Дальше:
<a href="lesson.html">атари, лестница и сеть</a>.
</p>
<div class="lesson-nav">
<a class="btn btn--quiet" href="learn.html">Все разделы</a>
<button class="btn btn--primary" type="button" data-lesson-done="dame">
Отметить пройденным
</button>
<a class="btn" href="lesson.html">Урок 2: атари →</a>
</div>
</article>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,161 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Играть против ИИ — Иго · школа Го</title>
<meta
name="description"
content="Партия Го против компьютера: доски 9×9, 13×13 и 19×19, шесть уровней ИИ, подсказки трёх лучших ходов."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html" aria-current="page">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="page-head">
<div class="container">
<p class="kicker">партия против компьютера</p>
<h1>Играть</h1>
<p>
Нажмите на пересечение, чтобы поставить камень. ИИ играет по правилам с захватом,
запретом самоубийства и простым ко.
</p>
</div>
</div>
<div class="container">
<div class="play-layout">
<div class="board-wrap">
<svg id="goban" role="application" aria-label="Игровая доска Го"></svg>
</div>
<aside class="play-panel">
<div class="card">
<h2>Настройки партии</h2>
<div class="settings-grid">
<div class="field">
<label for="board-size">Доска</label>
<select id="board-size" name="board-size">
<option value="9" selected>9 × 9</option>
<option value="13">13 × 13</option>
<option value="19">19 × 19</option>
</select>
</div>
<div class="field">
<label for="ai-level">Уровень ИИ</label>
<select id="ai-level" name="ai-level">
<option value="1">1 — новичок</option>
<option value="2" selected>2 — ученик</option>
<option value="3">3 — любитель</option>
<option value="4">4 — крепкий</option>
<option value="5">5 — сильный</option>
<option value="6">6 — мастер</option>
</select>
</div>
<div class="field">
<label for="color">Ваш цвет</label>
<select id="color" name="color">
<option value="black" selected>Чёрные</option>
<option value="white">Белые</option>
</select>
</div>
<div class="field">
<label for="komi">Коми</label>
<select id="komi" name="komi">
<option value="0">0</option>
<option value="5.5">5,5</option>
<option value="6.5" selected>6,5</option>
<option value="7.5">7,5</option>
</select>
</div>
</div>
</div>
<div class="card">
<h2>Пленники</h2>
<div class="captures">
<span class="side"
><span class="stone-dot black"></span> Чёрные взяли:
<strong data-captures="black">0</strong></span
>
<span class="side"
><span class="stone-dot white"></span> Белые взяли:
<strong data-captures="white">0</strong></span
>
</div>
</div>
<div class="status-line" data-status role="status">Новая партия. Ваш ход.</div>
<div class="card">
<h2>Три лучших хода</h2>
<ul class="hints-list" data-hints></ul>
</div>
<div class="card">
<h2>Управление</h2>
<div class="btn-row">
<button class="btn btn--small" type="button" data-action="pass">Пас</button>
<button class="btn btn--small btn--quiet" type="button" data-action="undo">
Отменить ход
</button>
<button class="btn btn--small btn--danger" type="button" data-action="resign">
Сдаться
</button>
<button class="btn btn--small btn--primary" type="button" data-action="new">
Новая партия
</button>
</div>
</div>
</aside>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
<script src="js/go.js"></script>
</body>
</html>

View file

@ -0,0 +1,105 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Прогресс — Иго · школа Го</title>
<meta
name="description"
content="Ваш прогресс в школе Го: пройденные уроки, решённые задачи, серия дней."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html" aria-current="page">Прогресс</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="page-head">
<div class="container">
<p class="kicker">тихий счёт упорству</p>
<h1>Прогресс</h1>
<p>
Всё считается локально, на этом устройстве. Ничего не отправляется на сервер — поэтому
цифры честные.
</p>
</div>
</div>
<div class="container" data-stats data-total-lessons="10" data-total-problems="12">
<div class="stats-grid">
<div class="card stat-card">
<div class="value" data-stat="lessons">0 / 10</div>
<div class="label">уроков пройдено</div>
</div>
<div class="card stat-card">
<div class="value" data-stat="problems">0 / 12</div>
<div class="label">задач решено</div>
</div>
<div class="card stat-card">
<div class="value" data-stat="streak">1</div>
<div class="label">дней подряд</div>
</div>
</div>
<div class="card" style="max-width: 640px">
<h2 style="margin-bottom: 14px">Путь по курсу</h2>
<div class="progress-track" role="progressbar" aria-label="Прогресс курса">
<div class="progress-fill"></div>
</div>
<p class="progress-note" data-stat="pct">Курс пройден на 0%</p>
<p class="progress-note">
Десять минут в день надёжнее трёх часов раз в месяц: Го растёт на регулярности, а не на
подвигах.
</p>
<div class="btn-row" style="margin-top: 16px">
<a class="btn btn--primary" href="learn.html">Продолжить курс</a>
<a class="btn btn--quiet" href="tsumego.html">К задачам</a>
</div>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,135 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Регистрация — Иго · школа Го</title>
<meta
name="description"
content="Регистрация в школе Го для синхронизации прогресса между устройствами."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html" aria-current="page">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="container">
<div class="card auth-card">
<h1>Регистрация</h1>
<p style="text-align: center; color: var(--ink-soft); font-size: 0.95rem">
Одна учётная запись — чтобы прогресс жил на всех устройствах. Никакой рассылки.
</p>
<form data-login-form novalidate>
<div class="field">
<label for="email">Электронная почта</label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
placeholder="you@example.com"
/>
<span class="error-text">Введите корректный адрес почты.</span>
</div>
<div class="field">
<label for="password">Пароль</label>
<input
type="password"
id="password"
name="password"
autocomplete="new-password"
placeholder="минимум 8 символов"
/>
<span class="error-text">Пароль не может быть пустым.</span>
</div>
<div class="field">
<label for="password2">Пароль ещё раз</label>
<input
type="password"
id="password2"
name="password2"
autocomplete="new-password"
placeholder="повторите пароль"
/>
<span class="error-text">Пароли не совпадают.</span>
</div>
<div class="field">
<label for="captcha">Код с картинки</label>
<div class="captcha-row">
<span class="captcha-box" data-captcha></span>
<button
class="btn btn--quiet btn--small"
type="button"
data-captcha-refresh
aria-label="Обновить код"
>
↻ обновить
</button>
</div>
<input
type="text"
id="captcha"
name="captcha"
inputmode="text"
autocomplete="off"
placeholder="символы с картинки"
style="margin-top: 10px"
/>
<span class="error-text">Введите код с картинки.</span>
</div>
<button class="btn btn--primary" type="submit" style="width: 100%">
Создать аккаунт
</button>
</form>
<div class="auth-links">
<span>Уже есть аккаунт? <a href="cabinet.html">Войдите</a></span>
</div>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,340 @@
<!doctype html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Цумэ-го — Иго · школа Го</title>
<meta
name="description"
content="Задачи цумэ-го с фильтрами по теме и сложности: жизнь и смерть, сэмэаи, лестницы, захват."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&family=PT+Serif:wght@400;700&family=PT+Mono&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<a class="logo" href="index.html"><span class="kanji"></span> Иго · школа Го</a>
<nav class="main-nav" aria-label="Основное меню">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html" aria-current="page">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="progress.html">Прогресс</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-label="Открыть меню" aria-expanded="false">
</button>
</div>
</div>
</header>
<main>
<div class="page-head">
<div class="container">
<p class="kicker">жизнь и смерть в миниатюре</p>
<h1>Цумэ-го</h1>
<p>
Задачи на локальную тактику. Ход чёрных — если не сказано иное. Решайте в уме, затем
проверяйте на доске в разделе «Играть».
</p>
</div>
</div>
<div class="container">
<form class="card filters" data-tsumego-filters aria-label="Фильтры задач">
<div class="field">
<label for="f-theme">Тема</label>
<select id="f-theme" name="theme">
<option value="all">Все темы</option>
<option value="life">Жизнь и смерть</option>
<option value="capture">Захват</option>
<option value="ladder">Лестницы</option>
<option value="semeai">Сэмэаи</option>
</select>
</div>
<div class="field">
<label for="f-diff">Сложность</label>
<select id="f-diff" name="difficulty">
<option value="all">Любая</option>
<option value="1">★ 1 — разминка</option>
<option value="2">★ 2 — легко</option>
<option value="3">★ 3 — средне</option>
<option value="4">★ 4 — трудно</option>
<option value="5">★ 5 — для упёртых</option>
</select>
</div>
<div class="field">
<label for="f-status">Статус</label>
<select id="f-status" name="status">
<option value="all">Все</option>
<option value="unsolved">Нерешённые</option>
<option value="solved">Решённые</option>
</select>
</div>
<div class="field">
<button class="btn btn--quiet btn--small" type="reset">Сбросить</button>
</div>
</form>
<div class="problems-grid">
<div
class="card problem-card"
data-problem="p01"
data-theme="capture"
data-difficulty="1"
>
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="d4,c5,e5"
data-white="d5"
data-mark="d6"
></div>
</div>
<h3>Последнее дыхание</h3>
<div class="meta">
<span class="stars"><span class="off">★★★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div
class="card problem-card"
data-problem="p02"
data-theme="capture"
data-difficulty="1"
>
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="b2,c3,b4"
data-white="b3"
data-mark="a3"
></div>
</div>
<h3>Камень у края</h3>
<div class="meta">
<span class="stars"><span class="off">★★★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p03" data-theme="life" data-difficulty="2">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="b2,c2,d2,b3,d3,b4,c4"
data-white="c3,d4"
data-mark="e3"
></div>
</div>
<h3>Один глаз или два?</h3>
<div class="meta">
<span class="stars">★★<span class="off">★★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p04" data-theme="ladder" data-difficulty="2">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="e5,f4"
data-white="e4,f5,g6"
data-mark="d4"
></div>
</div>
<h3>Лестница работает?</h3>
<div class="meta">
<span class="stars">★★<span class="off">★★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p05" data-theme="life" data-difficulty="3">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="b2,c2,d2,e2,b3,e3"
data-white="c3,d3,b4,c4,d4,e4"
data-mark="c5"
></div>
</div>
<h3>Гнутая четвёрка</h3>
<div class="meta">
<span class="stars">★★★<span class="off">★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p06" data-theme="semeai" data-difficulty="3">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="d3,d4,d5,e5"
data-white="e3,e4,f4,f5"
data-mark="c4"
></div>
</div>
<h3>Гонка на захват</h3>
<div class="meta">
<span class="stars">★★★<span class="off">★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div
class="card problem-card"
data-problem="p07"
data-theme="capture"
data-difficulty="3"
>
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="c4,d3,e4,d5"
data-white="d4,c5"
data-mark="e5"
></div>
</div>
<h3>Сеть вместо лестницы</h3>
<div class="meta">
<span class="stars">★★★<span class="off">★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p08" data-theme="ladder" data-difficulty="4">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="e5,f4,g3"
data-white="e4,f5,c7"
data-mark="d3"
></div>
</div>
<h3>Лестница с приманкой</h3>
<div class="meta">
<span class="stars">★★★★<span class="off"></span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p09" data-theme="semeai" data-difficulty="4">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="c3,d3,e3,c4,e4"
data-white="c5,d5,e5,d4"
data-mark="f4"
></div>
</div>
<h3>Четыре против трёх</h3>
<div class="meta">
<span class="stars">★★★★<span class="off"></span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p10" data-theme="life" data-difficulty="5">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="b2,c2,d2,e2,f2,b3,f3"
data-white="c3,d3,e3,b4,c4,d4,e4,f4"
data-mark="g3"
></div>
</div>
<h3>Шесть в углу живут?</h3>
<div class="meta"><span class="stars">★★★★★</span><span data-solved-slot></span></div>
</div>
<div
class="card problem-card"
data-problem="p11"
data-theme="capture"
data-difficulty="2"
>
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="d6,e5"
data-white="d5,e6,f6"
data-mark="c5"
></div>
</div>
<h3>Двойное атари</h3>
<div class="meta">
<span class="stars">★★<span class="off">★★★</span></span
><span data-solved-slot></span>
</div>
</div>
<div class="card problem-card" data-problem="p12" data-theme="life" data-difficulty="1">
<div class="thumb">
<div
class="goban-static"
data-size="9"
data-black="b2,c2,d2,b4,c4,d4"
data-white="b3,c3,d3"
data-mark="e3"
></div>
</div>
<h3>Прямая тройка</h3>
<div class="meta">
<span class="stars"><span class="off">★★★★</span></span
><span data-solved-slot></span>
</div>
</div>
</div>
<div class="empty-state" data-empty-state style="display: none">
<span class="glyph">○ ●</span>
<p>
<strong>Под фильтры ничего не подошло.</strong><br />Ослабьте условия — или отметьтесь в
разделе «Прогресс», если решили всё.
</p>
</div>
</div>
</main>
<footer class="site-footer">
<div class="container">
<span class="privacy-note"
>◎ Без сбора данных: прогресс хранится только на вашем устройстве.</span
>
<nav aria-label="Меню в подвале">
<ul>
<li><a href="learn.html">Курс</a></li>
<li><a href="tsumego.html">Цумэ-го</a></li>
<li><a href="play.html">Играть</a></li>
<li><a href="cabinet.html">Кабинет</a></li>
</ul>
</nav>
<span>© 2026 Иго</span>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,61 @@
# Дизайн: вход через VK ID и Яндекс ID (этап 8.2, запланирован)
Дата: 2026-08-06. Статус: дизайн (реализация — отдельной командой, когда
владелец получит ключи). Оба провайдера — российские юрлица; зарубежные
провайдеры исключены решением владельца.
## Что нужно от владельца (действия вне репозитория)
1. **VK ID**: кабинет https://id.vk.com/business/go → создать приложение
(веб-сайт) → получить `client_id` и `client_secret` (secure key) →
указать redirect URI: `https://<домен>/api/oauth/vk/callback.php`.
2. **Яндекс ID**: https://oauth.yandex.ru → новое приложение → права:
«Доступ к адресу электронной почты» (login:email), при желании
«имя и аватарка» → получить `ClientID`/`Client secret` → redirect
URI: `https://<домен>/api/oauth/yandex/callback.php`.
3. Вписать ключи в `config.php` на хостинге (не в репо!):
`'vk_client_id'`, `'vk_client_secret'`, `'yandex_client_id'`,
`'yandex_client_secret'`.
4. Домен обязан быть на HTTPS — оба провайдера требуют https redirect
URI (включается панелью хостинга, не .htaccess).
## Архитектура (когда будем делать)
- Таблица `oauth_accounts(user_id INTEGER, provider TEXT
('vk'|'yandex'), provider_user_id TEXT, PRIMARY KEY(provider,
provider_user_id))`; связь с users — по email (провайдер отдаёт
подтверждённый email → ищем/создаём users-запись, пароль при этом
NULL-able или случайный хэш — вход по паролю для такой учётки
отключён до установки пароля через «сменить пароль»).
- Эндпоинты: `GET /api/oauth/<provider>/start.php` → 302 на
авторизацию провайдера (state = random в сессии, anti-CSRF);
`GET /api/oauth/<provider>/callback.php?code&state` → проверка state
→ обмен code на token (server-side, client_secret в теле POST) →
получение email (VK: метод id.vk.com/oauth2/user_info; Яндекс:
https://login.yandex.ru/info?format=json с Bearer) → find-or-create
user + oauth_accounts → сессия → 302 на /account.html.
- VK ID — OAuth 2.1 + PKCE (code_verifier/code_challenge в сессии);
Яндекс — классический OAuth 2.0 code flow.
- Кнопки «Войти через VK ID» / «Войти через Яндекс ID» на /account.html
и /register.html; официальные бренд-ресурсы кнопок — из пресс-китов
провайдеров (лицензии — в CREDITS.md).
- Rate limit на start/callback; ошибки провайдера → /account.html с
тихой пометкой «вход через … не удался».
## Правовые заметки (не юридическая консультация)
- VK ID и Яндекс ID — сервисы российских юрлиц (ООО «В Контакте»,
ООО «Яндекс»); требование владельца «без зарубежной аутентификации»
соблюдено.
- 152-ФЗ: email/идентификаторы — персональные данные; при целевой
аудитории РФ рекомендуется хостинг с серверами в РФ и уведомление
Роскомнадзора как оператора ПДн (если применимо к проекту — вопрос
к юристу; сайт учебный, без монетизации).
- В privacy-заметку сайта (если появится) — перечислить, что храним:
email, хэш пароля, прогресс обучения.
## Объём реализации (оценка этапа 8.2)
server/: oauth/start+callback ×2 провайдера, миграция oauth_accounts,
smoke (моки token/userinfo через GOLEARN_TEST-хук); apps/web: кнопки,
строки; docs: обновить deploy.md (шаги ключей). ~1 сессия.

62
docs/plan-phase-6.md Normal file
View file

@ -0,0 +1,62 @@
# План этапа 6 — курс и цумэ-го
Дата: 2026-08-06. Статус: в работе. Предшественник: этап 5 (игровой UI)
завершён, main = 4aee5b7 + hotfix play.html.
## Ключевые решения (не пере-решать)
1. Контент — оригинальный, авторский (не скачиваем чужие сборники цумэ-го):
это снимает лицензионный вопрос; атрибуция — CREDITS.md. Спека допускает
«свободная лицензия с атрибуцией ЛИБО формат импорта»; собственный
контент строго безопаснее (ADR-0001, I.16).
2. Интерактив в Markdown — raw HTML-элемент `<go-task data-task-id="…">`,
гидратация клиентским скриптом страницы. Никаких remark/rehype-плагинов
и новых зависимостей. Теория читаема без JS (запасной текст внутри
элемента).
3. Критерий успеха задачи — goal-предикат, вычисляемый движком
(apps/web/src/lib/goals.ts поверх @go-learn/core), НЕ сравнение с
координатами решения. Дерево вариантов в SGF — только автоответы
соперника и подсказки.
4. Формальный глаз — упрощение v1 (пустая область 13 пункта, окружённая
одним цветом); ложный глаз не отличаем, уроки про ложный глаз строятся
на capture-target. Уточнение — через ADR, не в этой фазе.
5. Прогресс — ключ `go-learn:progress` (ProgressV1) в том же конверте
version+checksum, API — расширение существующего storage.ts. Серия дней —
чистая функция touchStreak.
6. Зона работы — apps/web + CREDITS.md + docs; packages/board и
packages/ai не трогаем (goal-предикаты живут в apps/web). Исключение по
ADR-0003: в core добавлена одна аддитивная функция buildPosition
(parseSgf не умеет AB/AW), разметка connect — CR вместо MA.
7. Параллельные ветки: stage-6-core (механика: goals, storage, виджет,
страницы, строки) и stage-6-content (уроки, цумэ-го, CREDITS,
контентные тесты). Мерж: core → content.
## Объём
- Механика: goals.ts (4 предиката + формальные глаза), прогресс в storage.ts,
виджет задачи (канва board, автоответы по дереву, сброс/подсказка),
страницы /learn, /learn/[id], /tsumego, /progress, навигация, strings.ts.
- Контент: 10 уроков (по разделу каждый, программа из мастер-промпта),
≥30 цумэ-го (difficulty 15, ≥3 на градацию), задачи для уроков
(≥1 интерактив там, где уместно), CREDITS.md.
- Тесты: goals (все предикаты + ложный/формальный глаз), storage roundtrip
прогресса, touchStreak (вчера/сегодня/разрыв), валидация контента (все
TaskV1 парсятся, SGF легальны движком, goal-разметка присутствует, id
уникальны, у уроков валидный frontmatter и существующие task-id),
wiring-страниц.
## Приёмка этапа
- npm run gates зелёные (0 warnings), astro build PASS.
- ≥10 уроков, ≥30 цумэ-го, фильтры работают, прогресс и серия дней
сохраняются в localStorage (roundtrip).
- Страница урока читаема без JS (проверка отключением гидратации).
- Вес страниц остаётся скромным (жёсткий бюджет ≤300 КБ — гейт этапа 7, но
не раздуваем заранее).
## Риски
- suggestDeadGroups грубовата (PROJECT_STATE) — two-eyes предикат пишем
собственный, на неё не опираемся.
- Контент-объём большой: качество SGF важнее количества сверх минимума;
валидационный тест — обязательный страховочный гейт.

View file

@ -0,0 +1,40 @@
# План фазы 1: этап 1 — движок правил (packages/core)
Обновлено: 2026-08-05 | Статус: завершена (этап 1 принят, гейты зелёные)
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Профиль проекта M, триаж литании — ADR-0001.
- Подсчёт — китайские правила (Tromp-Taylor), не японские: однозначно алгоритмизируется.
- Группы/дамэ: flood fill или union-find — выбрать по бенчмарку в этапе 1, выбор — в ADR.
- Ко: простое ко + позиционное суперко (оба, суперко нужен для китайских правил).
- Определение мёртвых групп — отдельная функция + ручная корректировка игроком,
не авто-анализ.
## Объём этапа 1
1. Доски 9×9, 13×13, 19×19; позиция — типизированный массив.
2. Группы и дамэ, снятие камней, запрет самоубийства (кроме самоубийства с захватом),
простое ко и суперко.
3. Подсчёт Tromp-Taylor; коми 6.5 (19×19) / 5.5 (9×9); сэки обрабатывается корректно.
4. SGF FF[4]: парсер и генератор (ходы, вариации, комментарии, разметка LB/TR/SQ/CR).
5. История с откатом и ветвлением.
6. Контракты — сначала в docs/INTERFACES.md, потом код.
7. Бенчмарк flood fill vs union-find → ADR-0002.
## Тесты (гейт приёмки этапа)
- Захват камня и группы; запрет самоубийства; самоубийство с захватом (легально);
простое ко; суперко; сэки при подсчёте; ничья.
- Wiring: orphan-check «кто вызывает» по каждой публичной функции ядра
(`gotcha-green-modules-dead-system`).
- Интеграционный тест полной партии (по заранее записанной SGF).
- Property-based тесты инвариантов (fast-check): инварианты — в docstring функций.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. К этапу 2 не переходим без этого.

View file

@ -0,0 +1,59 @@
# План этапа 10 — дизайн-перенос (Kimi WebSites → каркас Astro)
Дата: 2026-08-06. Ветка интеграции: main.
## Контекст
Пользователь получил от Kimi WebSites переносной пакет дизайна
(«васи + гобан», светлая тема, индиго + терракота). Эталон распакован и
сохранён в `docs/design/kimi-export/`. Задача — натянуть дизайн на
существующий каркас, не трогая логику, контент, API и тесты.
Контракт: `docs/INTERFACES.md`, раздел «Дизайн-перенос (этап 10)» —
токены (с 3 правками контраста), каркас Layout, тема доски, изображения,
паттерны страниц, критерии приёмки.
## Стадии
### Стадия 0 (оркестратор, сделано)
- Распаковка пакета, сжатие изображений в `apps/web/public/assets/`
(hero 53КБ / ink 118КБ / stones 24КБ).
- Эталон в `docs/design/kimi-export/`, контракт в INTERFACES.md.
### Стадия 1 — параллельные кодеры (3 ветки)
- **design-a (фундамент)**: Layout.astro (глобальные стили = адаптация
kimi-export/style.css + правила [hidden]/go-task, шапка/подвал/проп
section/мобильное меню/шрифты), packages/board render.ts DEFAULT_THEME →
светлая, index.astro (hero+features+ink-band+home-strip). Файлы:
`apps/web/src/layouts/Layout.astro`, `packages/board/src/render.ts`,
`apps/web/src/pages/index.astro`, при необходимости
`apps/web/src/ui/strings.ts` (только новые строки главной).
- **design-b (контентные страницы)**: learn.astro, learn/[id].astro,
tsumego.astro, progress.astro — разметка по паттернам контракта,
логика/id/data-атрибуты не меняются.
- **design-c (страницы приложения)**: play.astro, account.astro,
register.astro, password-reset.astro — то же правило.
Пересечений по файлам нет (Layout только у A). Общий API — проп
`section` Layout, описан в контракте.
### Стадия 2 — интеграция (оркестратор)
- Слияние веток в main, разрешение конфликтов.
- Гейты, точечные правки (wiring/тема-тесты).
- Проверка веса страниц dist и контраста.
- Сборка dist → `/mnt/agents/output/app`, НОВАЯ версия превью
(website_version_manager) — старая рабочая версия остаётся для сравнения.
### Стадия 3 — ритуал закрытия
- PROJECT_STATE.md (0.3.0), отчёт session-10, docs(sync) коммит, zip.
## Риски
- Слияние: Layout меняет только A — конфликтов не ожидается; strings.ts
могут трогать A и B — B не трогает strings (правило в брифе).
- Контраст исправлен на уровне токенов — регрессий быть не должно.
- Вес главной: hero 53КБ + CSS/JS — в бюджете.

View file

@ -0,0 +1,60 @@
# План этапа 11 — аудит контента, страница «Источники», дисклеймер
Дата: 2026-08-07. Инициатор: владелец. Ветка интеграции: main.
## Вводные
Владелец спросил, насколько можно доверять обучающему контенту. Ответ:
движок и проверка задач — машинно проверены; проза уроков и база цумэ-го
написаны ИИ без рецензии сильного игрока. Решение владельца:
1. Провести аудит контента по авторитетным источникам (обязательно).
2. Сделать на сайте отдельную страницу «Источники» — только голые
проверяемые факты. Чего не нашли — пишем «не найдено». Выдумывать
источники и факты КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО (требование владельца).
3. Добавить дисклеймер об открытом редактировании курса.
## Стадии
### Стадия 0 (оркестратор)
- Контракт в INTERFACES.md (этап 11): правила аудита, формат страницы
источников, текст и размещение дисклеймера.
### Стадия 1 — аудит (3 параллельных research-агента)
- audit-1: уроки 0103 (дамэ/захват, атари/лестница/сеть, самоубийство/ко).
- audit-2: уроки 0407 (глаза, подсчёт/коми, связь/разрезание, фусэки).
- audit-3: уроки 0810 (дзёсэки — конкретные последовательности!, форма,
ёсэ) + специфические числовые утверждения всего курса.
Каждый агент: читает markdown уроков из репо, извлекает проверяемые
утверждения, ищет подтверждения в авторитетных источниках (Sensei's
Library, Wikipedia, BGA/AGA, gobase и т.п.), КАЖДЫЙ источник обязан
реально открыть инструментом и процитировать подтверждающую строку.
Вердикты: ПОДТВЕРЖДЕНО (URL + цитата) | НЕ НАЙДЕНО | РАСХОЖДЕНИЕ (что
именно расходится). Отчёт — markdown-файл docs/audit/NN.md, коммит в
ветку audit/rN.
### Стадия 2 — интеграция (оркестратор + coder)
- Сверка отчётов, выборка подтверждённых источников (только реально
открытые URL — оркестратор перепроверяет выборочно сам).
- Страница /istochniki.html: по темам — факт + ссылка; раздел «что не
удалось подтвердить» честно перечислен.
- Дисклеймер: на странице курса и в футере (ссылка на «Источники»).
- Если аудит нашёл РЕАЛЬНЫЕ ошибки контента — отдельный список владельцу;
правки контента только с подтверждающим источником.
### Стадия 3 — приёмка и ритуал
- Гейты, вес, контраст; скриншот страницы источников; новая версия
превью; PROJECT_STATE, отчёт, zip.
## Риски
- Главный риск — галлюцинация источников агентами. Митигация: правило
«URL только из реально открытых страниц, с цитатой» + выборочная
перепроверка оркестратором через web_open_url.
- Sensei's Library и gobase могут быть медленными/недоступными — тогда
честный «не найдено» по части утверждений.

View file

@ -0,0 +1,70 @@
# План фазы 2: этап 2 — доска Canvas (packages/board)
Обновлено: 2026-08-05 | Статус: код и тесты готовы, gates зелёный (74 теста:
43 core + 31 board). Осталось: визуальная проверка — на этапе 5.
## Пометки о ходе (2026-08-05)
- Объём выполнен целиком: geometry.ts, render.ts (тёмная тема по умолчанию),
input.ts, dom.ts, index.ts; корневой tsconfig.json — reference на
packages/board; зависимость @go-learn/core 0.1.0 в package.json пакета.
- Все тесты из раздела «Тесты» ниже написаны и зелёные (31 тест board).
- Отклонение: в packages/board/tsconfig.json нет references на ../core —
`tsc -b --noEmit` (гейт typecheck) с project references падает с чистого
состояния (TS6310, CLI --noEmit распространяется на referenced-проект,
TS 5.9.3). Импорт резолвится через workspace-симлинк; подробности —
комментарий в packages/board/tsconfig.json.
- Уточнение семантики territory (в первой редакции контракта стороны не
различались): структура TerritoryMap { black, white } с множествами ключей
"x,y" по цветам — зафиксировано в контракте (правка main loop при приёмке).
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Доска — Canvas 2D, не DOM/SVG (плавность на 361 пересечении) — мастер-промпт.
- render(ctx, position, options) — чистая функция состояния, весь кадр из аргументов.
- Геометрия, редьюсер ввода и рендер — headless-тестируемые; DOM — тонкий адаптер
(setupCanvas, attachPointerInput), юнит-тестами не покрывается — см. матрицу
«чего НЕ покрывает ни один тест» ниже.
- Координаты — буквы без «I» (стандарт Го), числа снизу вверх.
- Дефолтная тема — тёмная (мастер-промпт: минималистичный дизайн, тёмная тема).
- Контракт — docs/INTERFACES.md, раздел «Контракт отрисовки доски» (зафиксирован
2026-08-05 до кода).
## Объём этапа 2
1. packages/board: geometry.ts (computeGeometry, pointToPixel, pixelToPoint,
hoshiPoints), render.ts (render + BoardTheme + дефолтная тёмная тема),
input.ts (reduceInput), dom.ts (setupCanvas, attachPointerInput), index.ts.
2. Отзывчивый размер и devicePixelRatio (setupCanvas).
3. Хоси, координаты и номера ходов (переключаемые), фантомный камень,
подсветка последнего хода, тач-ввод с подтверждением (первый тап — позиция,
второй тап или confirm — ход), слои разметки (LB/TR/SQ/CR, заливка
территории, приглушение мёртвых).
4. Корневой tsconfig: references += packages/board; packages/board зависит от
@go-learn/core (project reference).
## Тесты (гейт приёмки этапа)
- Геометрия: хоси для 9/13/19; roundtrip pixelToPoint∘pointToPixel; отклонение
точки дальше половины клетки; padding при showCoordinates on/off.
- Редьюсер ввода: тап → pending; второй тап по той же точке → commit; тап по
другой точке → перенос; confirm без pending → commit null; cancel → сброс;
тап мимо доски → без изменений.
- Рендер: мок-ctx с записью вызовов — число камней = числу занятых клеток
BoardState; фантом рисуется с phantomOpacity; метка последнего хода только
при lastMove; LB/TR/SQ/CR из markup; заливка только для territory-ключей;
мёртвые приглушены. Не проверяем каждый вызов — только инварианты.
- Wiring: orphan-check по публичным функциям packages/board (вызывающие —
тесты допустимы, дом-адаптер помечен как входная точка).
- Матрица «чего НЕ покрывает ни один тест»: реальный вид кадра, pointer-события
браузера, поведение на мобильном Safari — ручная проверка в этапе 5/7
(смоук-сценарий, слой GUI — litany II.8).
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. Визуальная проверка — на этапе 5
(когда появится страница); до неё — demo-страница не нужна.

View file

@ -0,0 +1,53 @@
# План фазы 3: этап 3 — ИИ ур. 13, эвристика (packages/ai)
Обновлено: 2026-08-05 | Статус: выполнен (gates зелёные; perf chooseMove 19×19 ≈ 8 мс)
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Отдельный пакет packages/ai — core остаётся чистыми правилами (рекомендация
отчёта сессии 2, принято).
- ИИ — синхронный чистый вычислитель; воркер появится в этапе 4 (MCTS),
протокол postMessage фиксируется там же.
- Случайность — только seeded Rng из core, передаётся параметром (детерминизм
= воспроизводимые партии и тесты).
- Легальность — только через applyMove из core; дублирование правил запрещено.
- Приоритеты и вероятности уровней — по контракту в docs/INTERFACES.md
(зафиксирован 2026-08-05 до кода).
- Простая эвристика глаза: все ортогональные соседи — свои камни, без анализа
диагоналей; осознанно грубо, ручной игры «подложить себе» бот не имитирует.
## Объём этапа 3
1. packages/ai (@go-learn/ai 0.1.0, зависимость @go-learn/core):
chooseMove + LEVEL_ATARI_PROBABILITY + типы по контракту.
2. Внутренние эвристики: поиск групп в атари (groupAt/liberties из core),
ходы-спасения (>1 дамэ после хода или контрзахват), ходы-захвата,
фильтр своего глаза, кандидаты nearby (Chebyshev ≤ 2), взвешенный выбор.
3. Корневой tsconfig: references += packages/ai (без project reference на core —
TS6310, см. PROJECT_STATE; резолв через workspace-симлинк).
## Тесты (гейт приёмки этапа)
- Детерминизм: одинаковый seed → идентичная последовательность ходов в полной
партии бот-бот (два прогона, сравнение SGF или списка ходов).
- Приоритеты: сконструированные позиции — при rng «всегда 0» (ролл < p)
бот спасает своё атари / забирает чужое; при rng «всегда 0.99» (ролл ≥ p)
приоритет пропускается.
- Фильтр глаза: бот не заполняет свой глаз при наличии других ходов.
- Легальность: все ходы бота в длинных партиях легальны (property: случайные
партии бот-бот до двух пасов, seed запинен; ни одного ok:false).
- Завершение: партия бот-бот 9×9 завершается (over) за разумное число ходов;
после over вызов chooseMove кидает Error.
- Perf-smoke: chooseMove на позиции 19×19 средней игры < 200 мс в CI-среде
(критерий приёмки «ур.3 < 50 мс на среднем ноутбуке» замер фактический,
число в отчёте сессии; порог в тесте с запасом, машинно-зависимый).
- Wiring: orphan-check по экспортам packages/ai.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. Полная партия 9×9 бот-бот
завершается корректно (предпосылка критерия приёмки v1 №2 для ур. 5 в этапе 4).

View file

@ -0,0 +1,81 @@
# План фазы 4: этап 4 — ИИ ур. 46, 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).

View file

@ -0,0 +1,82 @@
# План фазы 5: этап 5 — игровой интерфейс (apps/web, Astro)
Обновлено: 2026-08-05 | Статус: завершён (гейты зелёные, astro build собирает dist)
## Итоговые пометки (факт против плана)
- Astro запинен **5.18.2**, а не 7.1.6: astro 6/7 жёстко требуют Node
> =22.12.0 (bin/astro.mjs, проверено по tarball), в окружении Node 20.
> 5.18.2 — последняя линия с engines `^20.3.0`. Конфиг и объём — по плану
> (output: 'static', без интеграций).
- Зона расширена на 3 корневых конфига игноров: eslint.config.js,
.prettierignore, .gitignore — по одной записи `**/.astro/` (генерируемый
astro каталог ломал lint/format).
- AiClient дополнен `readonly fallback` (предусмотрено текстом контракта);
loadSettings/saveSettings — необязательным параметром StorageLike.
- Воркер собран Vite (chunk ai-worker-*.js в dist). Смоук dist в браузере:
доска рендерится, ход игрока + ответ бота работают.
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Astro 7.1.6 (версия сверена npm view 2026-08-05), output: 'static', без
UI-фреймворков — vanilla TS + DOM (спека: «без тяжёлых UI-фреймворков»).
- Строки UI — один ресурсный файл src/ui/strings.ts с первого дня (спека).
- Отмена хода бота — terminate + пересоздание воркера (решение по известной
проблеме cancel в реальном Worker, отчёт сессии 4).
- Режим подсказки: topMoves последнего MCTS-ответа как разметка + оценка;
дельта = изменение winRate лучшего хода между запросами (упрощение —
оценка с точки зрения стороны, чей ход анализировался; зафиксировано).
- Схема localStorage и AiClient — контракты в docs/INTERFACES.md
(зафиксированы 2026-08-05 до кода).
- Страницы: / — заглушка-визитка со ссылкой на партию; /play — игра.
Курс — этап 6, полировка (a11y, .htaccess) — этап 7.
## Объём этапа 5
1. apps/web (Astro): package.json @go-learn/web 0.1.0, astro.config.mjs
(static), tsconfig (extends базового, lib DOM), workspaces += "apps/*".
2. Страница /play: доска (packages/board render + reduceInput + setupCanvas +
attachPointerInput), панель: выбор доски 9/13/19, уровня 16, цвета
(чёрные/белые/случайно), коми (auto/число), кнопки «Пас», «Сдаться»,
«Отменить ход», «Новая партия»; счётчики пленников; статус-строка
(чей ход, причина отказа из MoveError, итог партии).
3. Подсчёт по окончании (два паса): scorePosition + отметка мёртвых
(suggestDeadGroups + ручной toggle по клику) → итог.
4. SGF: «Скачать SGF» (serializeSgf, blob-скачивание), «Загрузка SGF» —
режим просмотра (parseSgf, навигация назад/вперёд без игры).
5. Подсказки ур. 4+ (переключатель, hintsEnabled): топ-3 хода маркерами с
оценкой; дельта winRate.
6. 19×19 при ур. 46 — честное предупреждение (спека этапа 4).
7. Тёмная минималистичная тема (согласована с палитрой packages/board),
адаптивность desktop+мобильные.
8. lib/storage.ts (по контракту), lib/ai-client.ts (по контракту),
lib/game-store.ts (чистый, без DOM: GameTree + действия), ui/strings.ts.
9. Воркер: entry ai-worker.ts (строка подключения — комментарий в
packages/ai/src/worker.ts), сборка воркера средствами Astro/Vite.
## Тесты (гейт приёмки этапа)
- storage: roundtrip save→load; битый JSON/checksum/неизвестная version →
дефолты + recovered; миграция-путь задокументирован.
- game-store (чистый): новая партия, ход игрока через pending→confirm,
отказ с reason при нелегальном, пас×2 → фаза подсчёта, toggle мёртвой
группы, итог, resign → result, отмена хода, viewer: загрузка SGF,
навигация prev/next.
- ai-client: фейк-worker (инжекция) — маршрутизация ур. 13 в синхронный
движок, ур. 46 в воркер; cancel → AiCancelledError; фолбэк при отказе
воркера.
- Матрица «чего НЕ покрывает ни один тест»: реальный кадр канвы в браузере,
pointer на тачах, реальный Worker, astro build — смоук этапа 7 +
ручная проверка владельцем.
- vitest.config: include += apps/*/src.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный + `astro build` собирает dist.
Критерий v1 №4 («чистый клон собирается») проверяется сборкой в этой фазе;
№2 (партия против ур.5) и №5 (мобильный Safari) — ручная приёмка владельцем
после фазы 57.

View file

@ -0,0 +1,62 @@
# План этапа 6 — курс и цумэ-го
Дата: 2026-08-06. Статус: завершён (413/413, dist 280 КБ). Предшественник: этап 5 (игровой UI)
завершён, main = 4aee5b7 + hotfix play.html.
## Ключевые решения (не пере-решать)
1. Контент — оригинальный, авторский (не скачиваем чужие сборники цумэ-го):
это снимает лицензионный вопрос; атрибуция — CREDITS.md. Спека допускает
«свободная лицензия с атрибуцией ЛИБО формат импорта»; собственный
контент строго безопаснее (ADR-0001, I.16).
2. Интерактив в Markdown — raw HTML-элемент `<go-task data-task-id="…">`,
гидратация клиентским скриптом страницы. Никаких remark/rehype-плагинов
и новых зависимостей. Теория читаема без JS (запасной текст внутри
элемента).
3. Критерий успеха задачи — goal-предикат, вычисляемый движком
(apps/web/src/lib/goals.ts поверх @go-learn/core), НЕ сравнение с
координатами решения. Дерево вариантов в SGF — только автоответы
соперника и подсказки.
4. Формальный глаз — упрощение v1 (пустая область 13 пункта, окружённая
одним цветом); ложный глаз не отличаем, уроки про ложный глаз строятся
на capture-target. Уточнение — через ADR, не в этой фазе.
5. Прогресс — ключ `go-learn:progress` (ProgressV1) в том же конверте
version+checksum, API — расширение существующего storage.ts. Серия дней —
чистая функция touchStreak.
6. Зона работы — apps/web + CREDITS.md + docs; packages/board и
packages/ai не трогаем (goal-предикаты живут в apps/web). Исключение по
ADR-0003: в core добавлена одна аддитивная функция buildPosition
(parseSgf не умеет AB/AW), разметка connect — CR вместо MA.
7. Параллельные ветки: stage-6-core (механика: goals, storage, виджет,
страницы, строки) и stage-6-content (уроки, цумэ-го, CREDITS,
контентные тесты). Мерж: core → content.
## Объём
- Механика: goals.ts (4 предиката + формальные глаза), прогресс в storage.ts,
виджет задачи (канва board, автоответы по дереву, сброс/подсказка),
страницы /learn, /learn/[id], /tsumego, /progress, навигация, strings.ts.
- Контент: 10 уроков (по разделу каждый, программа из мастер-промпта),
≥30 цумэ-го (difficulty 15, ≥3 на градацию), задачи для уроков
(≥1 интерактив там, где уместно), CREDITS.md.
- Тесты: goals (все предикаты + ложный/формальный глаз), storage roundtrip
прогресса, touchStreak (вчера/сегодня/разрыв), валидация контента (все
TaskV1 парсятся, SGF легальны движком, goal-разметка присутствует, id
уникальны, у уроков валидный frontmatter и существующие task-id),
wiring-страниц.
## Приёмка этапа
- npm run gates зелёные (0 warnings), astro build PASS.
- ≥10 уроков, ≥30 цумэ-го, фильтры работают, прогресс и серия дней
сохраняются в localStorage (roundtrip).
- Страница урока читаема без JS (проверка отключением гидратации).
- Вес страниц остаётся скромным (жёсткий бюджет ≤300 КБ — гейт этапа 7, но
не раздуваем заранее).
## Риски
- suggestDeadGroups грубовата (PROJECT_STATE) — two-eyes предикат пишем
собственный, на неё не опираемся.
- Контент-объём большой: качество SGF важнее количества сверх минимума;
валидационный тест — обязательный страховочный гейт.

View file

@ -0,0 +1,50 @@
# План этапа 7 — полировка
Дата: 2026-08-06. Статус: завершён (431/431, dist 296 КБ). Предшественник: этап 6 завершён,
main = 2b249a5 (413/413, dist 280 КБ).
## Ключевые решения (не пере-решать)
1. .htaccess — apps/web/public/.htaccess (Astro копирует public/ в dist
как есть); содержимое зафиксировано в INTERFACES.md; HTTPS-редирект
сознательно ОТСУТСТВУЕТ (только панель хостинга,
gotcha-https-redirect-proxy).
2. Клавиатурный ввод — НЕ новый код в packages/board: чистый редьюсер
курсора в apps/web/src/lib/keyboard.ts, подтверждение хода идёт через
существующий reduceInput (tap по курсору → фантом → повтор → commit).
Семантика подтверждения едина с тач-вводом (I.6, одна механика).
3. ARIA-live — role="status" polite на существующих статусных строках,
без live-region-спама: меняется только текстовое содержимое.
4. Бюджет 300 КБ — по сырым байтам dist (с запасом к gzip); тест считает
html + локальные ассеты, на которые страница ссылается.
5. Отметка «урок пройден» — ручная кнопка + markLessonDone в storage.ts;
авто- done для уроков с задачами (этап 6) сохраняется, кнопка —
дополнение, не замена.
6. Зона — только apps/web; packages и корневые конфиги не трогаем.
## Объём
- public/.htaccess + wiring-проверка директив в dist.
- keyboard.ts (reduceCursor, кламп, тесты) + подписки в play-page.ts и
task-widget.ts (курсор, стрелки/Enter/Space/Escape, aria-labels).
- prefers-reduced-motion блок, :focus-visible, контроль контраста
(--muted при недоборе), role="status" на статусах.
- Кнопка «Отметить пройденным» на странице урока + markLessonDone + тест.
- Wiring-тест бюджета веса уроков ≤300 КБ.
- Строки — strings.ts.
## Приёмка этапа (и критерии v1, что можно проверить автоматически)
- npm run gates зелёные, 0 warnings; astro build PASS.
- dist/.htaccess с требуемыми директивами; без RewriteCond %{HTTPS}.
- Каждая страница урока ≤ 300 КБ по тесту.
- Критерии v1: №1 (тесты) и №4 (чистый клон собирается) — автоматически;
№2 (партия vs ур.5), №3 (тайминги бота), №5 (мобильный Safari) —
ручные, владельцу.
## Риски
- Клавиатурный курсор не должен ломать pointer-ввод: общий InputState,
подписки независимы.
- suggestDeadGroups грубовата (PROJECT_STATE) — в этап 7 НЕ входит
(отдельная задача через ADR при жалобах на подсчёт).

View file

@ -0,0 +1,47 @@
# План этапа 8.1 — регистрация отдельной страницей, восстановление пароля, капча
Дата: 2026-08-06. Статус: завершён (467/467 + smoke 58 OK). Предшественник: этап 8 завершён,
main = 7da814c (461/461 + smoke OK). Решения владельца: своя автономная
капча; OAuth — позже (дизайн docs/design/oauth-vk-yandex.md).
## Ключевые решения (не пере-решать)
1. Регистрация — ОТДЕЛЬНАЯ страница /register.html (не модалка): проще,
доступнее (a11y), диплинкабельна. /account.html становится страницей
входа со ссылками «Нет аккаунта? Зарегистрируйтесь» и «Забыли
пароль?».
2. Капча — автономная SVG (БЕЗ GD): glyph-повроты + шумовые кривые;
код в сессии (sha256, 10 мин), одноразовая; только на регистрации
(login прикрыт rate limit). SVG выбран вместо GD: GD не везде есть
на хостингах, а локальный рантайм из deb — тем более.
3. Восстановление — токен 1 час, sha256 в БД, request всегда 200 (нет
enumeration), сбой mail() → 503; тестовый режим GOLEARN_TEST=1:
письмо в файл, капча-код в заголовке X-Captcha-Debug.
4. Параллельные ветки: stage-8-1-server (эндпоинты + миграция
password_resets + smoke) и stage-8-1-web (страницы + строки);
зоны не пересекаются. Мерж: server → web.
## Объём
- server/: api/captcha.php, api/password-reset/request.php,
api/password-reset/confirm.php; register.php += captcha; миграция
password_resets; config.example.php += base_url, mail_from;
smoke.sh += сценарии капчи и восстановления.
- apps/web: pages/register.html, pages/password-reset.html (два
режима по query), правки account.astro (ссылки), sync-client.ts
+= requestPasswordReset/confirmPasswordReset + captcha в register,
strings.ts.
- docs/deploy.md += шаги: проверка mail() (тестовое письмо), base_url
в config.php.
## Приёмка
- npm run gates зелёные; smoke.sh (с новыми сценариями) зелёный
локально; astro build PASS (18 страниц).
## Риски
- Доставляемость mail() с shared-хостинга (SPF/DKIM) — ручная проверка
владельцем на деплое; риск в PROJECT_STATE.
- SVG-капча слабее GD/сервисной к OCR-ботам — для учебного сайта
приемлемо; при злоупотреблениях — Yandex SmartCaptcha через ADR.

View file

@ -0,0 +1,50 @@
# План этапа 8 — личный кабинет и синхронизация прогресса
Дата: 2026-08-06. Статус: завершён (461/461 + smoke.sh OK). Предшественник: этапы 17 завершены,
main = a99aeb9 (431/431). Активация — команда владельца; модель учётки и
до-триаж раздела V — ADR-0004.
## Ключевые решения (не пере-решать)
1. Модель — email+пароль (ADR-0004); БЕЗ верификации почты и
восстановления пароля (нет SMTP на shared-хостинге; предупреждение в
UI при регистрации).
2. PHP 8 без фреймворка, PDO SQLite (WAL, busy_timeout, БД вне web root),
prepared statements везде, лимит тела 256 КБ, rate limit по IP,
SameSite=Lax + заголовок X-GoLearn-Client как CSRF-мера. Синтаксис
PHP ≥ 8.0 — совместимость с хостингами.
3. Прогресс на сервере — opaque ProgressV2 (ProgressV1 + updatedAt),
last-write-wins по updatedAt; localStorage — SoT для гостей;
миграция V1→V2 в storage.ts по контракту конверта.
4. Превью статическое: кабинет деградирует тихо при недоступном API
(status 'unavailable', пометка в UI, без алертов).
5. Локальная проверка бэкенда: PHP 8.2 CLI (распакованные deb-пакеты в
/tmp/debs, обёртка /tmp/php-run) + php -S + curl smoke.sh. В среде
нет системного PHP — гейты npm PHP не запускают.
6. Параллельные ветки: stage-8-server (server/) и stage-8-web (apps/web);
общих файлов нет. Мерж: server → web.
## Объём
- server/: api/{register,login,logout,me,progress,health}.php,
lib/{db,http,auth,ratelimit}.php, config.example.php, .htaccess
(заголовки, запрет листинга), tests/smoke.sh.
- apps/web: storage.ts (ProgressV2 + миграция), sync-client.ts
(mergeDecision чистая + fetch), pages/account.astro, nav + strings.
- docs/deploy.md — деплой-чеклист V.2 (артефакты, порядок, smoke,
откат, пути на хостинге, бэкап/restore SQLite).
- PROJECT_STATE: этап 8 разморожен → в работе → завершён.
## Приёмка этапа
- npm run gates зелёные (0 warnings), astro build PASS.
- server/tests/smoke.sh зелёный локально (PHP 8.2): полный прогон
register/login/put/get/401/403/429/health.
- Ручная приёмка владельцем на хостинге — по docs/deploy.md.
## Риски
- PHP на хостинге владельца может отличаться (версия, pdo_sqlite) —
чек-лист deploy.md начинается с проверки `php -v` и модулей.
- Сессии PHP на shared-хостинге: gc и путь сессий — зафиксировать
session_save_path в config при проблемах (заметка в deploy.md).

51
docs/reports/session-1.md Normal file
View file

@ -0,0 +1,51 @@
# Отчёт сессии 1 (для следующей сессии)
Дата: 2026-08-05 | Ветка: main | Последний коммит: см. git log
## Что сделано
1. **Шаг 0 — каркас.** Триаж литании (ADR-0001, профиль M), git + .gitattributes
(`* text=auto eol=lf`) с первого коммита, docs-каркас, AGENTS.md (точка входа),
гейты `npm run gates` (prettier + eslint + tsc -b --noEmit + vitest).
Запинены: typescript 5.9.3, vitest 4.1.10, eslint 10.8.0, prettier 3.9.6,
fast-check 4.9.0. CI — GitHub Actions (ubuntu+windows), написан, не запускался.
2. **Этап 1 — packages/core.** Движок правил полностью по контракту
docs/INTERFACES.md: доски 9/13/19, группы/дамэ (flood fill — ADR-0002,
бенчмарк: union-find медленнее в 1.74×), захваты, запрет самоубийства
(самоубийство с захватом легально), простое ко + позиционное суперко,
подсчёт Tromp-Taylor со снятием dead-камней и ничьей, история-дерево с
ветвлением, SGF FF[4] (парсер+генератор, roundtrip), seeded RNG (mulberry32).
Тесты: 43/43 (юнит, wiring orphan-check, интеграция полной партии 9×9,
property-based fast-check).
3. **Решения владельца (молчание = дефолт):** каркас подтверждён, коми 13×13 = 5.5,
LICENSE = MIT (файл добавлен), CI на GitHub Actions.
## Что НЕ сделано
- Этапы 29 не начаты.
- CI не проверен на GitHub (репо локальный).
- Настоящая позиция двойного ко для юнит-теста суперко (см. PROJECT_STATE).
## Подводные камни для следующей сессии
- **Среда выполнения:** `/mnt/agents/output` НЕ поддерживает симлинки →
`npm install` невозможен там. Работать только через git worktree в `$HOME`
(`git worktree add $HOME/work-<name> <branch>` из /mnt/agents/output/go-learn).
Каталог `$HOME` может сбрасываться между сессиями — worktree пересоздавать,
коммиты в общем .git не теряются. `git worktree prune` не запускать, пока
живы субагенты; мёртвые записи удалять `git worktree remove --force <path>`.
- **У субагентов своя файловая система** (или $HOME сбрасывается): НЕЗАКОММИЧЕННЫЕ
правки в worktree теряются. Правило: перед передачей работы субагенту всё
коммитить; контракты передавать и в тексте задания, не только файлами.
- Контракт core уже зафиксирован — изменения только через обновление
INTERFACES.md ПЕРЕД кодом. UI этапа 5 ветвится по полю `reason` (MoveError),
не по тексту error.
- В /mnt-репозитории были мусорные пустые каталоги от ошибочной команды с
brace-expansion (шелл без bash) — вычищены в сессии 1; mkdir писать без `{}`.
## С чего начать продолжение
Этап 2 (доска Canvas): сначала дописать контракт рендера в docs/INTERFACES.md
(render(ctx, position, options) — чистая функция, devicePixelRatio, хоси,
тач-ввод), затем код. План — docs/plans/plan-phase-1.md обновить или создать
plan-phase-2.md.

View file

@ -0,0 +1,75 @@
# Отчёт сессии 10 — этап 10: дизайн-перенос «васи»
Дата: 2026-08-06. Ветка main: 0a5e2bd → 3f8fc5b (+docs(sync)). Версия 0.3.0.
## Вводные
Пользователь получил от Kimi WebSites переносной пакет дизайна
(`/mnt/agents/upload/Иго-сайт-перенос.md`: 8 HTML-страниц, style.css ~23 КБ,
2 JS, 3 JPG в base64) и попросил натянуть его на наш каркас. Эталон
распакован и сохранён в `docs/design/kimi-export/` (только как референс, в
сборку не входит).
## Что сделано
1. **Контракт** (INTERFACES.md, «Дизайн-перенос (этап 10)»): токены,
каркас Layout, тема доски, паттерны страниц, приёмка. Три сознательных
отклонения от палитры эталона ради WCAG AA ≥ 4.5:1 (замерено расчётом):
`--ink-faint` #a29885#6e675c, `--terra` #c25a33#a84e2c,
`--ok` #4a7a4e#46734a.
2. **Изображения**: hero/ink/stones сжаты (276→53 КБ, 215→118 КБ,
96→24 КБ) в `apps/web/public/assets/`.
3. **Три параллельных кодера** (ветки design/a,b,c, слиты --no-ff):
- A: Layout.astro — глобальные стили = адаптация style.css целиком +
`[hidden]` + go-task на светлых токенах; шапка-стекло с канji 碁,
проп `section` → aria-current, мобильное меню, подвал, Google Fonts
(PT Sans/Serif/Mono); DEFAULT_THEME доски → светлая; главная по
эталону (hero-meta с честными числами: 10/33/6).
- B: learn, урок, tsumego, progress — разметка по паттернам.
- C: play, account, register, password-reset — разметка по паттернам.
4. **Интеграционные правки оркестратора** (найдены скриншот-проверкой):
- страницы потока B без `.container` — контент уезжал в полную ширину;
- сортировка уроков по `data.order` не работала (во frontmatter у всех
уроков `order: 1` — исторически) → сортировка по id файла;
- shiki рендерил ASCII-диаграммы тёмной темой поверх светлого pre →
`markdown.shikiConfig.theme: 'github-light'` в astro.config;
- дублирующий H1 из тела markdown → скрыт через
`.lesson-body > :global(h1:first-child)` (scoped-стили Astro не
достают до разметки `<Content/>` — важный нюанс, записать в опыт).
## Проверки
- gates: 467/467 (28 файлов), format+lint+typecheck зелёные — после
каждого слияния и финально.
- Вес страниц (HTML+CSS+JS+img eager): максимум ~157 КБ (index с hero);
ink/stones — loading="lazy". Бюджет ≤300 КБ выполнен.
- Контраст ключевых пар ≥ 4.5:1 (расчёт по токенам + отчёт кодера A).
- Скриншот-проверка в браузере: index, learn, урок, tsumego, play,
account, register — соответствуют эталону; логика (id, data-атрибуты,
островки) на месте.
## Доставка
- dist → /mnt/agents/output/app, сохранена **новая версия превью**
(website_version_manager, id b0b2da7). Прежняя рабочая версия не
тронута — можно сравнить.
## Опыт сессии
- Scoped-стили .astro не действуют на разметку из `<Content/>` и
`set:html` — использовать `:global(...)` с узким селектором.
- Shiki в Astro по умолчанию тёмный: при смене темы сайта не забыть
`shikiConfig.theme`.
- /mnt FS: повторяющиеся сбои `index.lock`/`HEAD.lock` при git-операциях —
лечатся повтором; stale HEAD.lock удалять вручную после проверки, что
git-процессов нет. merge лучше делать маленькими порциями.
## Открыто
- Тексты кикеров/абзацев этапа 10 живут прямо в разметке страниц
(помечены комментариями «до переноса в strings.ts») — кандидат на
чистку при следующем касании.
- Поле `order` во frontmatter уроков у всех = 1 (не используется;
порядок — по id). Либо проставить 1..10, либо убрать из схемы.
- Восстановление пароля/капча в статическом превью не работают (API на
хостинге) — by design, картинка капчи в превью битая.

View file

@ -0,0 +1,96 @@
# Отчёт сессии 11 — этап 11: аудит контента, страница «Источники», дисклеймер
Дата: 2026-08-07. Ветка main: 32199a6 → 66dc7a5 (+docs(sync)). Версия 0.3.1.
## Вводные
Владелец спросил, насколько можно доверять обучающему контенту, и принял
решение: (1) обязательный аудит курса по авторитетным источникам; (2) отдельная
страница «Источники» — только голые проверяемые факты, что не нашли — честно
«не найдено», выдумывать КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО; (3) дисклеймер об открытом
редактировании. Железное правило зафиксировано в INTERFACES.md (этап 11).
## Что сделано
1. **Контракт** (INTERFACES.md, «Аудит контента, страница „Источники“,
дисклеймер (этап 11)», 4b6bf80): правило реально открытых URL с цитатой,
формат отчётов и страницы, приёмка с выборочной перепроверкой оркестратором.
2. **Аудит — 3 параллельных research-агента** (ветки audit/r1..r3, слиты
--no-ff; отчёты `docs/audit/01-03.md`, `04-07.md`, `08-10.md`):
- 85 проверяемых утверждений: **70 подтверждено, 10 расхождений,
5 не найдено**. Источники: правила AGA (зеркала BGA и cs.cmu.edu),
Sensei's Library, rusgolib.gofederation.ru.
- Каждое подтверждение — URL + дословная цитата из реально открытой
страницы.
3. **Перепроверка оркестратором** (приёмка контракта): открыто и сверено
с цитатами 5 URL из отчётов (Atari, Calling Out Atari, Ladder,
Bamboo Joint, Dango — все Sensei's Library); дополнительно curl'ом
подтверждены ключевые для правок страницы ChineseRules (коми 7.5, cost 0),
Komi и 34PointHighApproachAttachDrawbackJoseki (цукэ-хики). Часть страниц
SL/BGA в момент проверки отвечала таймаутами — то же наблюдали аудиторы.
4. **Дисклеймер** (32199a6): примечание на /learn.html («курс создан с
помощью ИИ и находится в открытом редактировании…») со ссылкой на
«Источники»; пункт «Источники» в футере.
5. **Страница /istochniki.html** (218c292): 8 тематических разделов,
формат «факт одной строкой — ссылка на первоисточник», 50 ссылок
(все — только из аудит-отчётов, `target="_blank" rel="noopener"`),
обязательный честный раздел «Что пока не удалось подтвердить» (5 пунктов).
Строки вынесены в `strings.ts` (раздел `sources`).
6. **Правки уроков по подтверждённым расхождениям** (218c292, 9 файлов):
- урок 01: «атари» вслух — практика обучающих партий, опытные не объявляют;
- урок 02: атари вынуждает, но не обязывает отвечать; «сирэ» →
«ситё-прерыватель» (ladder breaker);
- урок 03: нелегальный ход по AGA отменяется и засчитывается как пас;
- урок 05: ход в свою территорию — 1 очко только по японским правилам,
по китайским (нашим) счёт тот же; про коми добавлено: наши 6,5/5,5,
стандарт китайских — 7,5;
- урок 06: изолированное косуми локально неразрезаемо (миаи);
- урок 07: неподтверждённые числа камней → канонические «стены 2/3/4»;
убран термин «линия смерти»;
- урок 08: «ханэ у подошвы 3-4» → цукэ-хики с верными ролями цветов
(цукэ/хики — чёрные, ханэ — белые);
- урок 09: «танэки» → «такэфу»; «столб» → «данго» (пельменная форма);
диаграммы тигровой пасти и бамбука приведены к собственному тексту;
- урок 10: убран неподтверждённый диапазон «13 очка» и суперлатив
«самый частый приём».
## Проверки
- gates: **467/467** (28 файлов), format+lint+typecheck зелёные — прогон
оркестратором в чистом worktree на merge-коммите 66dc7a5.
- Сборка: 19 страниц, `dist/istochniki.html` на месте; eager-вес страницы
источников ~38 КБ (HTML 20 КБ + общий CSS 18 КБ) — бюджет ≤300 КБ с
большим запасом.
- Скриншот-проверка /istochniki.html в браузере: шапка, разделы, ссылки —
в порядке.
- Контрактная перепроверка ≥5 URL — выполнена (см. выше).
## Доставка
- dist → /mnt/agents/output/app (rsync с `--exclude=.git`), сохранена
**новая версия превью** (website_version_manager, id 70226a8).
## Опыт сессии
- Sensei's Library периодически отвечает таймаутами на web_open_url —
аудиторам и оркестратору стоит делать повторы и дублировать проверку
curl'ом; недоступность источника фиксировать честно («не найдено»).
- Аудит-отчёты от субагентов пришли неформатированными — prettier --check
падал; механическая нормализация docs/audit/*.md (только формат, без
изменения текста) включена в коммит 218c292.
## Открыто
- **Расхождение коми**: движок и INTERFACES.md держат 6,5 (19×19) по решению
владельца от 2026-08-05, а стандарт китайских правил — 7,5. Урок теперь
честно разводит «наши партии» и «стандарт». Сменить ли дефолт движка на
7,5 — отдельное решение владельца (затронет контракт, тесты подсчёта,
сохранённые партии).
- Поле `order` во frontmatter уроков у всех = 1 (не используется; порядок —
по id). Либо проставить 1..10, либо убрать из схемы.
- Тексты кикеров этапа 10 частично живут в разметке (помечены «до переноса
в strings.ts»).
- Этап 8.2 (OAuth VK ID / Yandex ID) ждёт ключи приложений от владельца;
дизайн — docs/design/oauth-vk-yandex.md. Иностранные провайдеры запрещены.
- Этап 9 (KataGo ONNX) заморожен; деплой на хостинг и ручная приёмка —
«немного попозже» (владелец).

54
docs/reports/session-2.md Normal file
View file

@ -0,0 +1,54 @@
# Отчёт сессии 2 (для следующей сессии)
Дата: 2026-08-05 | Ветка: main | Этап 2 (доска Canvas) — завершён
## Что сделано
1. **Контракт packages/board** зафиксирован в docs/INTERFACES.md до кода
(геометрия, рендер, редьюсер ввода, DOM-адаптер).
2. **packages/board реализован:** geometry.ts (computeGeometry, pointToPixel,
pixelToPoint, hoshiPoints 19→9/13→5/9→5), render.ts (render — чистая функция,
11 слоёв в контрактном порядке, дефолтная тёмная тема, координаты без «I»,
хоси, фантом, метка последнего хода, номера ходов, разметка LB/TR/SQ/CR,
заливка территории TerritoryMap, приглушение мёртвых), input.ts (reduceInput:
тап → фантом, повторный тап или confirm → ход), dom.ts (setupCanvas с DPR,
attachPointerInput с отпиской).
3. **Тесты: 74/74** (43 core + 31 board): геометрия, таблица переходов редьюсера,
инварианты рендера через мок-рекордер ctx, orphan-check.
4. **Интеграционные правки main loop при приёмке:**
- TerritoryMap { black, white } вместо префиксной кодировки "w:x,y" —
контракт синхронизирован;
- eslint: `no-undef: off` для TS-файлов (typescript-eslint рекомендация,
DOM-глобалы видит только tsc) — костыльные eslint-disable убраны;
- в контракте board типы ключей приведены к string (типа PointKey в core нет);
- вычищены пустые мусорные каталоги `{docs…` от команды с brace-expansion.
## Что НЕ сделано
- Этапы 39 не начаты.
- Визуальная проверка кадра, pointer-события, мобильный Safari — смоук этапа 5/7
(записано в матрице «чего НЕ покрывает ни один тест», plan-phase-2).
- CI на GitHub не запускался (репо локальный).
## Подводные камни для следующей сессии
- Среда: работать только через `git worktree add $HOME/work-<name> <branch>` из
/mnt/agents/output/go-learn; $HOME субагента/сессии может быть чистым —
worktree пересоздавать (`git worktree remove --force <старый путь>` если
«already checked out»), коммиты в общем .git не теряются. Незакоммиченное
теряется — коммитить перед концом работы. Контракты дублировать в тексте
задания субагенту, не только файлами.
- `tsc -b --noEmit` несовместим с project references (TS6310, TS 5.9.3) —
packages/board без reference на core, резолв через workspace-симлинк.
Не «чинить» добавлением reference — сломает гейт с чистого состояния.
- docs/*.md коммитить только после prettier --write (format-гейт ловит и md).
- mkdir без фигурных скобок — шелл без brace-expansion плодит мусорные каталоги.
## С чего начать продолжение
Этап 3 (ИИ ур. 13, эвристика): сначала контракт модуля ИИ в docs/INTERFACES.md
(выбор хода по эвристикам: спасти своё атари → забрать чужое → взвешенный
случайный ход вблизи хода противника → не заполнять свой глаз → пас; уровни
0.3/0.6/0.9; RNG — createSeededRng из core), затем код. Решить и записать:
packages/ai отдельным пакетом или модулем в core — рекомендация: отдельный
packages/ai (core остаётся чистыми правилами). Создать docs/plans/plan-phase-3.md.

53
docs/reports/session-3.md Normal file
View file

@ -0,0 +1,53 @@
# Отчёт сессии 3 (для следующей сессии)
Дата: 2026-08-05 | Ветка: main | Этап 3 (ИИ ур. 13, эвристика) — завершён
## Что сделано
1. **Контракт packages/ai** зафиксирован в docs/INTERFACES.md до кода:
chooseMove, AiLevel 13, LEVEL_ATARI_PROBABILITY {1:0.3, 2:0.6, 3:0.9},
приоритеты save-atari → capture-atari → nearby (Chebyshev ≤ 2) → random →
pass, фильтр своего глаза, легальность только через applyMove из core.
2. **packages/ai реализован** (@go-learn/ai 0.1.0): ai.ts (chooseMove, роллы
rng в фиксированном порядке — позиция в потоке не зависит от доски),
candidates.ts (легальные + фильтр глаза), tactics.ts (атари, спасение
по факту «>1 дамэ после хода» — контрзахват покрыт без частных случаев),
pick.ts (равномерный/взвешенный выбор, nearby с весами 2/1).
3. **Тесты: 99/99** (74 + 25 новых): приоритеты на сконструированных позициях
с rng-заглушками (0 / 0.99), фильтр глаза, Error при over, детерминизм
(3 seed × 2 прогона полных партий), 12 партий бот-бот 9×9 до over ≤ 200
ходов с проверкой легальности каждого хода, perf-smoke, orphan-check.
4. **Perf: chooseMove 19×19 ≈ 7.7 мс** (критерий «ур.3 < 50 мс» с запасом).
5. Интеграционная правка main loop: return type в хелперах теста — lint
0 warnings (цель литании для нового кода).
## Что НЕ сделано
- Этапы 49 не начаты. Воркер и протокол postMessage — этап 4 (по контракту).
## Подводные камни для следующей сессии
- Среда: git worktree в $HOME, пересоздание (`git worktree remove --force` при
«already checked out»), коммитить всё перед концом — незакоммиченное теряется;
контракты дублировать в тексте задания субагенту.
- packages без project references на core (TS6310 при `tsc -b --noEmit`) —
резолв через workspace-симлинк; не «чинить».
- docs/*.md — через prettier --write перед коммитом (format-гейт).
- Ролл-семантика chooseMove задокументирована в шапке ai.ts: два ролла на ход
всегда — менять порядок/число роллов = менять воспроизводимость партий,
только через ADR.
- Порог perf-теста 200 мс — машинно-зависимый; не ужесточать в CI.
## С чего начать продолжение
Этап 4 (ИИ ур. 46, MCTS в Web Worker):
1. Сначала в docs/INTERFACES.md: протокол postMessage (запрос/ответ/отмена,
сериализация позиции, бюджет времени, уровень) И контракт движка MCTS
(UCT, плейауты по эвристикам этапа 3 — переиспользовать packages/ai).
2. Бюджеты: ур.4 — 300 мс, ур.6 — 1500 мс; таргет 9×9 и 13×13, на 19×19 —
честное предупреждение в UI (этап 5).
3. Только однопоточный код — SharedArrayBuffer требует COOP/COEP, которых
может не быть на шаред-хостинге.
4. Плейауты тоже через seeded Rng — реплеи MCTS воспроизводимы.
5. Создать docs/plans/plan-phase-4.md.

63
docs/reports/session-4.md Normal file
View file

@ -0,0 +1,63 @@
# Отчёт сессии 4 (для следующей сессии)
Дата: 2026-08-05 | Ветка: main | Этап 4 (MCTS ИИ ур. 46 + воркер) — завершён
## Что сделано
1. **Контракт MCTS + протокол postMessage** зафиксирован в docs/INTERFACES.md
до кода: chooseMoveMcts (UCT c=1.4, ленивая экспансия, лёгкий плейаут,
бюджеты {4:300, 5:800, 6:1500} мс, maxSimulations для тестов, topMoves до 3),
WorkerRequest/WorkerResponse (позиция — списком ходов, cancel → result).
2. **packages/ai/src/mcts.ts** — движок: лёгкая плейаут-политика (атари-реакция
вокруг последнего хода + случайные точки с фильтром глаза), оценка
scorePosition с komi, стоп по deadline/shouldStop/maxSimulations.
Детерминированная экспансия (захваты первыми) — задокументирована.
3. **worker-protocol.ts + worker.ts** — пересборка позиции из списка ходов с
валидацией, createWorkerHandler(port, deps?) с инжектируемыми часами;
браузерный self.onmessage опущен — подключает этап 5 (строка в комментарии).
4. **Тесты: 118/118** (+19): детерминизм, легальность 9×9/13×13, бюджет по
фейк-часам, shouldStop, захват в атари за 200 симуляций (smoke качества),
партия MCTS vs ур.3, протокол через фейк-порт, perf-smoke.
5. **Perf: ~147 симуляций за 300 мс на 9×9**; ур.6 на 13×13 ≈ 1500 мс wall —
критерий v1 №3 «<2 с» выполнен.
## Что НЕ сделано
- Этапы 59 не начаты. UI и реальное подключение воркера — этап 5.
## Подводные камни для следующей сессии (этап 5)
- **Cancel в реальном Worker**: синхронный handler не примет сообщение во время
счёта (event loop). Варианты: чанкование chooseMoveMcts через setTimeout в
воркере (меняет код движка/обёртки — через контракт!) или terminate воркера
- пересоздание (протокол stateless — позиция передаётся списком ходов,
поэтому пересоздание дёшево). Рекомендация: terminate+recreate, код не трогать.
- **Сила MCTS ограничена** ~147 сим/300 мс на 9×9 (applyMove копирует
positionHashes — O(n²) в плейаутах). Если на приёмке ур. 46 слабоваты —
отдельная задача оптимизации core (хэш инкрементальный), через ADR.
- **19×19 для MCTS**: UI обязан показать честное предупреждение (спека этапа 4);
рекомендуемые таргеты — 9×9 и 13×13.
- Среда: worktree в $HOME, пересоздание, коммитить всё; контракты дублировать
в задании; docs через prettier перед коммитом; project references не добавлять.
- Seed воркера: протокол передаёт seed в каждом choose — UI формирует
(например, seed партии + requestId); это часть воспроизводимости реплеев.
## С чего начать продолжение
Этап 5 (игровой интерфейс) — первый этап с реальным сайтом:
1. Astro в apps/web (проверить актуальную версию astro через npm view ДО
конфигов — gotcha-llm-stale-configs; статический выход, без SSR).
2. Страница партии: packages/board render + input-редьюсер, выбор
доски/уровня/цвета, коми (defaultKomi + ручное), пас/сдаться/отмена/новая,
счётчики пленников, скачивание SGF (serializeSgf) и загрузка для просмотра
(parseSgf), режим подсказки на ур. 4+ (topMoves из MctsResult, дельта
после хода игрока).
3. Подключение worker.ts: строка подключения — комментарий в worker.ts;
fallback: если Worker недоступен — синхронный вызов в основном потоке
(fail-safe, с пометкой в UI).
4. Строки UI — в один ресурсный файл с первого дня (спека).
5. Смоук вручную: визуальная проверка кадра из матрицы plan-phase-2.
6. Контракт: схема localStorage для настроек/прогресса партии — в INTERFACES.md
ДО кода (version + checksum + миграция + roundtrip-тест).
7. Создать docs/plans/plan-phase-5.md.

68
docs/reports/session-5.md Normal file
View file

@ -0,0 +1,68 @@
# Отчёт сессии 5 (для следующей сессии)
Дата: 2026-08-05 | Ветка: main | Этап 5 (игровой интерфейс) — завершён
## Что сделано
1. **Контракты этапа 5** в docs/INTERFACES.md до кода: схема localStorage
(конверт version+checksum FNV-1a, SettingsV1, recovered при битом состоянии)
и AiClient (ур. 13 синхронно, ур. 46 воркер, cancel=terminate+recreate,
fail-safe синхронный фолбэк с пометкой).
2. **apps/web (Astro 5.18.2, static, vanilla TS + DOM):**
- lib: storage.ts, ai-client.ts, game-store.ts (чистый: фазы
playing/scoring/viewer, гонка requestId, undo пары ходов, подсчёт с
toggle мёртвых, SGF-просмотр), territory.ts (карта территории для заливки);
- страницы: / (визитка) и /play (канва + полная панель: размер/уровень/
цвет/коми, чекбоксы, Пас/Сдаться/Отменить/Новая, счётчики пленников,
статус-строка с причинами MoveError, предупреждение 19×19 при ур. 46,
подсказки топ-3 с winRate и дельтой, скачивание/загрузка SGF);
- ui/strings.ts — все строки UI одним файлом; тёмная тёплая тема,
адаптивность.
3. **Тесты: 147/147** (+29: storage 7, ai-client 5, game-store 12, territory 3,
wiring 2), lint 0 warnings. **astro build PASS, dist 80K**, worker-chunk
9.75 kB. Браузерный смоук исполнителем: рендер доски, пас + ответ бота,
hidden-состояния.
4. Интеграционная правка main loop: astro 5.18.2 (MIT) добавлен в
THIRD_PARTY_LICENSES.md — лицензионный гейт.
## Принятые отклонения (задокументированы исполнителем)
- Astro 5.18.2 вместо 7.1.6: astro 6/7 требуют Node ≥22.12 (hardcoded в bin),
окружение Node 20.20.2. Записано в PROJECT_STATE.
- AiClient.fallback: boolean и StorageLike-инжекция в storage — дополнения
контрактов, поведение не меняют.
- eslint.config.js/.prettierignore/.gitignore += `**/.astro/`.
## Что НЕ сделано
- Этапы 69 не начаты.
- Ручная приёмка: критерии v1 №2 (партия vs ур.5 до подсчёта) и №5 (мобильный
Safari) — владельцем; визуальный смоук кадра — по матрице plan-phase-2.
## Подводные камни для следующей сессии (этап 6)
- Среда: worktree в $HOME, пересоздание, коммитить всё; контракты дублировать
в задании; docs через prettier; project references не добавлять; `.astro/`
в игноре — не пугаться генерируемого каталога.
- suggestDeadGroups грубовата (предлагает живые группы на разрежённых
позициях) — при работе с уроками «жизнь и смерть» это станет заметно;
кандидат на уточнение (через ADR, зона core).
- Строки UI — только через ui/strings.ts, новые строки туда же.
- Worker-chunk собирается Vite; при изменении packages/ai/src/worker.ts
пересобирать apps/web.
## С чего начать продолжение
Этап 6 (курс + цумэ-го):
1. Контракт форматов в docs/INTERFACES.md ДО кода: схема Markdown-урока
(разделы → уроки → шаги), формат интерактивной задачи (SGF-позиция +
условие успеха, проверяемое движком — не хардкод координат), схема
localStorage прогресса курса (version + checksum, ключ go-learn:progress).
2. Программа по спеке: дамэ/захват → атари/лестница/сеть → самоубийство и ко →
глаза, жизнь и смерть, ложный глаз → подсчёт/пас/конец → связь/разрезание →
углы-стороны-центр → 34 базовых дзёсэки → форма → простое ёсэ.
3. Цумэ-го ≥30 задач, фильтры, свободная лицензия с атрибуцией (CREDITS.md)
либо формат импорта.
4. «Мой прогресс»: уроки, задачи, серия дней.
5. Создать docs/plans/plan-phase-6.md.

76
docs/reports/session-6.md Normal file
View file

@ -0,0 +1,76 @@
# Отчёт сессии 6 (для следующей сессии)
Дата: 2026-08-06 | Ветка: main | Этап 6 (курс + цумэ-го) — завершён
## Что сделано
1. **Контракты этапа 6 в INTERFACES.md до кода** (раздел «Форматы данных
уроков и задач»): схема Markdown-урока (10 фиксированных разделов,
`<go-task data-task-id>` raw-HTML в теле), TaskV1 (goal-предикаты вместо
хардкода координат), ProgressV1 + touchStreak, страницы
/learn, /learn/[id], /tsumego, /progress. План — docs/plans/plan-phase-6.md.
2. **ADR-0003**: аудит контракта выявил, что parseSgf не умеет AB/AW и MA.
В core добавлена ОДНА аддитивная функция `buildPosition` (валидация:
вне доски/дубли/группа без дамэ → Error; корректный positionHashes);
разметка connect — CR вместо MA. +6 тестов core.
3. **Механика (подагент, ветка stage-6-core)**: goals.ts (4 предиката +
формальный глаз v1: пустая область 13 пункта, окружённая одним цветом),
storage.ts += ProgressV1 (тот же конверт), task-session.ts (чистая
машина: illegal/wrong с откатом/playing/solved, авто-ответ по дереву,
reset, hint), task-widget.ts (гидратация go-task, JSON-островок
#go-task-data), 4 страницы, content.config.ts (zod из astro:schema),
nav + строки в strings.ts.
4. **Контент (подагент, ветка stage-6-content)**: 10 уроков (300700 слов,
разделы 16 и 10 с интерактивом), 33 цумэ-го + 7 задач уроков
(capture 20 / connect 5 / two-eyes 5 / liberties 3; difficulty 15,
≥3 на градацию), валидационный content.test.ts (217 проверок: позиции
легальны buildPosition, вариации легальны applyMove, разметка цели),
CREDITS.md (контент оригинальный, CC BY 4.0).
5. **Интеграция (main loop)**: wiring-тесты переведены с placeholder на
реальный контент; добавлен тест «главная линия каждой задачи достигает
goal». Он поймал реальный дефект: goal проверялся только ПОСЛЕ
авто-ответа соперника → транзиентные цели (liberties) недостижимы.
Фикс task-session по контракту: проверка после КАЖДОГО хода игрока,
до авто-ответа. Placeholder-контент удалён.
6. **Гейты main: 413/413** (включая astro build в wiring-тесте),
0 lint warnings. dist = 15 страниц, 280 КБ. Превью обновлено
(version 89825fe).
## Принятые отклонения (подагенты задокументировали)
- env.d.ts в apps/web/src: tsc -b не видит сгенерированные типы astro:content
(каталог .astro/ в .gitignore) — модуль объявлен через entry astro.
- Для toPlay:'white' task-session инвертирует B/W в SGF перед parseSgf
(парсер моделирует от пустой доски с чёрных). Контент v1 весь
toPlay:'black' — инверсия не задействована.
- lessons.done = решены все lsn-задачи урока; чисто теоретические уроки
(79) пометки «пройден» не получают (в контракте нет UI для ручной
отметки) — кандидат на этап 7.
- Wiring-тест запускает реальный `npm run build` внутри vitest (timeout
240 с) — гейты включают сборку.
## Известные ограничения контента v1 (в PROJECT_STATE)
- Мотив снэпбэк невыразим (parseSgf падает на occupied при повторной игре
точки); тема «сеть» в цумэ-го отсутствует (есть в тексте урока 2);
у two-eyes задач авто-ответ иногда тэнуки.
## Подводные камни для следующей сессии (этап 7)
- Среда прежняя: worktree в $HOME, коммитить всё, контракты inline в
задании, prettier на docs, sh без brace expansion, no `worktree prune`.
- Удаление чужих worktree после мержа: `git worktree remove --force` +
при необходимости `rm -rf .git/worktrees/<name>`, затем `git branch -d`.
- Этап 7 по спеке: .htaccess (gzip, кэш, MIME application/wasm),
prefers-reduced-motion, клавиатурное управление доской, ARIA-live,
страница урока ≤ 300 КБ по сети, теория без JS (уже держим), HTTPS
ТОЛЬКО панелью хостинга (НЕ RewriteCond %{HTTPS} — вечный редирект за
SSL-прокси). Плюс кандидаты: ручная отметка «урок пройден», suggestDeadGroups
грубовата для уроков жизни и смерти (через ADR, зона core).
- Ручная приёмка владельцем: критерии v1 №2 (партия vs ур.5) и №5
(мобильный Safari) всё ещё открыты.
## С чего начать продолжение
Этап 7 (полировка) по мастер-промпту; docs/plans/plan-phase-7.md создать,
контракты .htaccess/a11y — в INTERFACES.md до кода.

62
docs/reports/session-7.md Normal file
View file

@ -0,0 +1,62 @@
# Отчёт сессии 7 (для следующей сессии)
Дата: 2026-08-06 | Ветка: main | Этап 7 (полировка) — завершён
## Что сделано
1. **Контракты этапа 7 в INTERFACES.md до кода** (раздел «Полировка:
.htaccess, a11y, бюджет веса») + docs/plans/plan-phase-7.md.
2. **.htaccess** (apps/web/public/.htaccess → dist): AddType
application/wasm, mod_deflate (html/css/js/json/svg), mod_expires
(_astro 1 год immutable, html no-cache, прочее 1 час), Options
-Indexes, DirectoryIndex index.html. HTTPS-редиректа НЕТ (комментарий
с gotcha-https-redirect-proxy). Wiring-тест проверяет директивы в dist.
3. **Клавиатура** (/play и go-task): чистый reduceCursor в
lib/keyboard.ts (кламп по краям, старт — центр), стрелки/Enter/Space/
Escape через существующий reduceInput (фантом → commit, единая
семантика с тачем); курсор — квадратный маркер accent через render.
4. **A11y**: role="status" aria-live="polite" на статусах партии/задачи/
отметки урока; prefers-reduced-motion глушит анимации; :focus-visible
outline; контраст посчитан: --text 13.2/11.6, --muted 5.64/4.97 —
≥4.5:1, осветление не потребовалось.
5. **Отметка «урок пройден»**: markLessonDone в storage.ts (чистая,
сохраняет tasksSolved авто-done этапа 6, streak через touchStreak) +
кнопка на странице урока.
6. **Бюджет веса**: wiring-тест — каждая страница урока ≤300 КБ (самая
тяжёлая ~10.3 КБ по формуле контракта, ~42 КБ с транзитивными чанками).
7. **Гейты main: 431/431** (+12 keyboard, +4 markLessonDone, +2 wiring),
0 lint warnings, astro build PASS, dist 296 КБ. Превью обновлено
(version 7f93e64).
## Инцидент среды
Финальное сообщение подагента stage-7 не доставлено (alive=0 при
посылке), но работа была полностью закоммичена в ветку stage-7 —
потерь нет. Правило «коммить всё» сработало как страховка. Независимая
проверка гейтов main loop выполнена после мержа.
## Состояние критериев приёмки v1
- №1 (тесты движка, суперко, сэки, wiring) — зелёные.
- №3 (тайминги) — частично: perf-smoke в packages/ai (ур.13 < 200 мс
среднее по 20 вызовам 19×19); полная проверка на ноутбуке владельца.
- №4 (чистый клон собирается) — проверяется каждым воркрием с нуля.
- №2 (партия vs ур.5 до подсчёта) и №5 (мобильный Safari) — ручные,
владельцу.
## Что осталось (этапы 17 завершены)
- Ручная приёмка владельцем (критерии №2, №3, №5).
- По желанию владельца: этап 8 (PHP+SQLite синхронизация прогресса,
ОПЦИОНАЛЬНО, требует до-триажа раздела V литании) и этап 9 (KataGo
ONNX, «этап мечты», не начинать до приёмки 17).
- Кандидаты на уточнение (через ADR, по жалобам): suggestDeadGroups
грубовата; контент v1 (снэпбэк невыразим, тема «сеть» только в тексте,
двухглазые авто-ответы иногда тэнуки); метрика веса — только прямые
ассеты (уточнить при росте).
## Подводные камни среды (без изменений)
Worktree в $HOME; коммитить всё перед делегированием и завершением;
контракты inline в задании; prettier на docs; sh без brace expansion;
не `git worktree prune`; чистка: remove --force + rm .git/worktrees/<n>.

View file

@ -0,0 +1,56 @@
# Отчёт сессии 9, этап 8.1 (для следующей сессии)
Дата: 2026-08-06 | Ветка: main | Этап 8.1 (кабинет 2.0) — завершён
## Что сделано
1. **Контракты 8.1** в INTERFACES.md + plan-phase-8-1.md; дизайн OAuth
VK ID / Яндекс ID — docs/design/oauth-vk-yandex.md (что нужно от
владельца: приложения в консолях id.vk.com/business/go и
oauth.yandex.ru, ключи в config.php, HTTPS redirect URI; реализация —
этап 8.2, ~1 сессия, когда будут ключи).
2. **server (подагент)**: api/captcha.php (SVG-капча БЕЗ GD: 5 символов,
повороты/шумовые кривые, sha256+expires 10 мин в сессии, одноразовая);
register += captcha (порядок: 429→403→422 captcha→422→409→201);
api/password-reset/request.php (всегда 200, anti-enumeration; токен
bin2hex(32), sha256 в БД, 1 час; mail() со ссылкой; сбой → 503;
5/10мин) и confirm.php (200/400/422; 10/10мин); миграция
password_resets; config.example.php += base_url/mail_from; тестовый
режим GOLEARN_TEST=1 (X-Captcha-Debug, письмо в GOLEARN_TEST_MAIL).
3. **web (подагент)**: /account.html — только вход + ссылки «Нет
аккаунта? Зарегистрируйтесь» и «Забыли пароль?»; новые страницы
/register.html (email, пароль ×2, капча с кнопкой обновления,
авто-login → /account) и /password-reset.html (два режима по
?token=); sync-client += requestPasswordReset/confirmPasswordReset,
register(email, password, captcha); все статусы role=status.
4. **Гейты main: 467/467** (+6), 0 warnings, astro build PASS
(18 страниц). **smoke.sh: 58 проверок**, прогнан main loop
независимо — SMOKE OK (капча, полный цикл reset: письмо→токен→новый
пароль→login старым 401/новым 200, 429-е).
5. docs/deploy.md += base_url/mail_from и шаг проверки почты (4.1).
## Отклонения исполнителей (приняты)
- confirm.php: 422 (пароль <8) проверяется до 400 token_invalid
контракт порядок не фиксировал, оба кода по контракту.
- rate_check в register — до проверки заголовка (403-пробы не тратят
лимит); запись попытки после заголовка.
- Тема письма — сырой UTF-8 без RFC 2047 (риск отображения темы на
части MTA; ссылка не страдает) — в PROJECT_STATE.
## Среда
- /tmp обнуляется между сессиями: PHP-рантайм (debs в /tmp/debs +
/tmp/php-run) пересоздавать; рецепт — в session-8.md. Статический
бинарь с dl.static-php.dev не докачивается (обрывы); deb-способ рабочий.
- Финальные сообщения подагентов второй раз приходят пустыми/теряются,
но «коммить всё» спасает: проверять ветки гитом перед повторной
делегацией.
## Что осталось
- Этап 8.2 (VK ID / Яндекс ID) — когда владелец получит ключи;
дизайн готов.
- Ручная приёмка: деплой по docs/deploy.md (включая проверку почты),
критерии v1 №2/№3/№5.
- Этап 9 (KataGo ONNX) — после приёмки 17.

57
docs/reports/session-8.md Normal file
View file

@ -0,0 +1,57 @@
# Отчёт сессии 8 (для следующей сессии)
Дата: 2026-08-06 | Ветка: main | Этап 8 (личный кабинет, синхронизация) — завершён
## Что сделано
1. **ADR-0004**: владелец выбрал email+пароль (отступление от «анонимного
токена» спеки); до-триаж раздела V литании выполнен и задокументирован
(IN: V.1, V.2, V.4, V.5, V.6-минимум, V.7-минимум, V.8; OUT: V.3,
внешняя наблюдаемость). Без верификации почты/восстановления пароля
(нет SMTP) — предупреждение в UI.
2. **Контракты этапа 8** в INTERFACES.md до кода: схема SQLite, 7
эндпоинтов, ProgressV2 (+updatedAt) с миграцией V1→V2, mergeDecision
(pull/push/noop/unauthorized/unavailable), тихая деградация.
3. **server/ (подагент stage-8-server)**: PHP 8 без фреймворка, PDO
SQLite (WAL, busy_timeout, foreign_keys, идемпотентная init-миграция),
prepared statements везде, 256 КБ лимит тела (413), rate limit 10/10мин
по IP (429), X-GoLearn-Client как CSRF-мера (403), security-заголовки,
config.example.php ([SECRET], config.php в .gitignore), health.php
(200 {"status":"ok"}; 503 при сбое БД без текста исключений).
smoke.sh: 30+ проверок — зелёный.
4. **apps/web (подагент stage-8-web)**: ProgressV2 + миграция + Clock-
инжекция, sync-client.ts (mergeDecision + fetch-обёртки + метка
серверного времени после push → следующая сверка noop), страница
/account (вход/регистрация/выход/ручной sync, role=status), навигация
«Кабинет». Интеграционная доработка main loop: тихий авто-sync на
/progress при активной сессии (до отрисовки цифр).
5. **docs/deploy.md** (V.2): 3 артефакта (dist → web root, server/ →
/api, config.php + БД вне web root), порядок, smoke, откат, бэкап/
restore SQLite, канонические пути — заполнить при первом деплое.
6. **Гейты main: 461/461**, 0 warnings, astro build PASS (16 страниц).
Smoke PHP прогнан main loop независимо — SMOKE OK.
## Среда: локальный PHP-рантайм
Системного PHP нет и apt недоступен. Решение: deb-пакеты Debian bookworm
(php8.2-cli/common/sqlite3) распаковываются dpkg-deb -x в /tmp/debs,
обёртка /tmp/php-run (pdo → pdo_sqlite → sqlite3, порядок важен).
ВНИМАНИЕ: /tmp между сессиями обнуляется — рантайм пересоздавать
(команды в отчёте кодера stage-8-server и в этом абзаце).
Статический бинарь с dl.static-php.dev не скачался (обрывы ~14 МБ).
## Известные ограничения (в PROJECT_STATE)
- Кабинет в статическом превью = «сервер синхронизации недоступен»
(by design; API оживает на хостинге).
- Риски хостинга: Secure-cookie за TLS-прокси без HTTPS=on; rate limit
общий за NAT; WAL на NFS медленный; Apache 2.2 игнорирует Require
(защита config.php — тогда БД строго вне web root).
## Что осталось
- Ручная приёмка владельцем: деплой на хостинг по docs/deploy.md +
критерии v1 №2/№3/№5.
- Этап 9 (KataGo ONNX) — «этап мечты», только после приёмки 17.
- Кандидаты через ADR: suggestDeadGroups грубовата; контент v1 (снэпбэк,
«сеть», тэнуки-ответы); метрика веса — только прямые ассеты.