40 Commits
Author SHA1 Message Date
andrey 3061247ce5 Паттерны и кампания: 8 новых фигур, 3 showcase-уровня, факты в README
- PATTERNS: авианосец, длинная лодка, каноэ, MWSS, HWSS, Loafer, кролики, ружьё Симкина (22 → 30)
- CAMPAIGN_LEVELS: ручные «Флотилия»/«Погоня»/«Кролики» после «Издалека» (100 → 103)
- README: раздел «Интересные факты» и список ресурсов
- tests: счётчики и проверки новых фигур/уровней синхронизированы
2026-10-13 15:06:52 +03:00
andrey d7f891f106 Паттерны: прокрутка своего списка колесом не закрывает его
Обработчик scroll (capture) ловил и прокрутку самого #patternMenu,
поэтому колесо, которым листают фигуры, тут же закрывало список.
Теперь scroll внутри списка игнорируется, закрытие — только при
прокрутке внешних контейнеров. Тест 35g2 дополнен проверкой колеса.
2026-10-13 11:48:58 +03:00
andrey 47e463c24a Паттерны: свой список выбора вместо нативного popup
Нативный popup <select> на длинном списке (22 фигуры) в Chromium
позиционируется поверх курсора: обычный клик выбирает случайную фигуру —
дропдаун «открывается на нажатии и закрывается на отпускании».

- #optPattern остаётся контрактом, но его popup подавлен (mousedown →
  preventDefault), показывается свой список #patternMenu (22 фигуры,
  5 групп), позиционируется рядом с селектом и никогда не поверх него;
- выбор фигуры пишет value в #optPattern и шлёт change (единый путь stamp),
  закрытие — по выбору/Escape/клику вне/resize/scroll;
- тест 35g2, доки.
2026-10-12 21:25:20 +03:00
andrey 7d92da6360 Кампания: «Готов» из дорисовки сразу запускает симуляцию
Замкнут цикл «нарисовал → побежало»: не нужна отдельная кнопка «Старт».
2026-10-12 16:52:57 +03:00
andrey 0d435a92c4 Кампания: авто-дорисовка и кнопка «Заново» с подтверждением
- после авто-паузы стабильного поля уровень сразу открывает EDIT_P1 (без клика «Дорисовать»)
- кнопка «Заново» в панели действий перезапускает уровень через диалог подтверждения
2026-10-12 16:49:31 +03:00
andrey bbfd36e63e Сплэш: короткое описание и выравненные кнопки режимов
- Описание на стартовом экране сведено к первому предложению
- «Кампания» и «Игра на двоих» — в одинаковом нейтральном стиле
- «Продолжить партию» выделена акцентной кнопкой
2026-10-12 16:24:46 +03:00
andrey df9ccc4cd7 Стартовый сплэш и отдельный экран «Игра на двоих»
- Меню стало сплэшем: краткое описание, тема, кнопки «Продолжить»/«Кампания»/«Игра на двоих»
- Новое состояние TWO_SETUP (экран #setup): своя справка и настройки поля (размер/torus/бюджет)
- У кампании своя сворачиваемая справка #aboutCampaign, стиль справок вынесен в .help
- Тесты адаптированы под новый поток + раздел 37n (сплэш/двухэкранность/reload)
2026-10-12 16:19:20 +03:00
andrey 126c7525fd Кампания: «Издалека» вставлен на 10-е место со сдвигом уровней
Вместо прежнего обмена id4 и id10 — вставка: «Издалека» (id4) стоит на
10-м месте, а id5..id10 сдвинуты на позицию вверх (индексы 3..8).
Порядок: 1,2,3,5,6,7,8,9,10,4,11..100.

- Меняется только порядок записей: данные уровней (имя, размер, зона,
  бюджет, клетки) не тронуты, «Издалека» как была.
- Единственный «блип» размеров теперь на индексе 9; тест монотонности
  усилен (исключается только он), добавлен кейс «порядок вставки».
- 464/464, паритет step 273/273 сохранён.
2026-10-12 15:43:09 +03:00
andrey 7b82898f27 Сенсорное управление: поворот паттерна кнопкой и постановка по отпусканию
- кнопка «↻» в тулбаре поворачивает штамп на 90° (альтернатива колесу мыши)
- тумблер «👆 тап»: нажатие — только подсветка, постановка/стирание по отпусканию
- тулбар переносится на узких экранах, доска не перекрывается
- глобалы tapPlace/pressing, тесты 36e–36j, доки
2026-10-12 13:42:13 +03:00
andrey 6518d70dd1 Паттерны: 12 новых фигур и группировка дропдауна по категориям
- Добавлены стойкие (ванна, лодка, кораблик, каравай, пожиратель, пруд),
  осцилляторы (маяк, часы, пентадекатлон), долгожители (жёлудь, Diehard,
  громовая птица); у записей появилось поле group.
- #optPattern сгруппирован в optgroup по PATTERN_GROUPS
  (Стойкие/Осцилляторы/Корабли/Долгожители/Ружья).
- Тесты: patternCells новых фигур, поведение (периоды/рост/гибель),
  группировка и наличие всех id; обновлены доки и комментарии.
2026-10-12 13:06:42 +03:00
andrey 41b0b37a64 Кампания: у сгенерированных уровней нет зоны на всё поле
У 96 сгенерированных уровней полная зона заменена на «центр»: теперь
зона рисования никогда не покрывает всю доску, остаётся 7 типов
(половина, центр, углы, полосы, прямоугольник). Полная зона — только
у ручных «Разминка» и «Рой».

- Правка точечная: изменены зоны 12 уровней, клетки врага и порядок
  уровней не тронуты («Издалека» без изменений).
- Генератор: kind «всё поле» перенаправлен в центр без сдвига PRNG;
  --check теперь запрещает полную зону у сгенерированных.
- Тесты: вместо «зоны разнообразны» — три проверки (нет полной зоны у
  сгенерированных; полная только у ручных; все 7 типов присутствуют).
- 424/424, паритет step 273/273 сохранён.
2026-10-12 13:00:03 +03:00
andrey ba4ac28d95 Кампания: 100 уровней с растущими картами и разными зонами
Расширили кампанию с 14 до 100 уровней: 4 ручных + 96 сгенерированных
(id 5..100). Размер карты растёт с ~60 до 300×300, бюджет и число фигур —
вместе с размером.

- Данные уровней переведены с ASCII-сеток enemy на компактный формат
  cells: [[x,y],...] (иначе 96 уровней до 300×300 раздували память и
  старт); enemyFromCells удалён, buildCampaignField строит поле из cells.
- Зоны рисования разнообразны: всё поле, половина, углы, центр, узкие
  полосы, произвольные прямоугольники; у части уровней враг вне зоны.
- Уровень «Издалека» перенесён на 10-е место (данные без изменений).
- Список из 100 уровней — прокручиваемый; syncLevelList не работает вне
  экрана выбора.
- tools/gen-campaign-levels.mjs обновлён под 96 уровней и формат cells
  (seeded PRNG, без Math.random, --check).
- Тесты обновлены под 100 уровней (422/422), паритет 273/273 сохранён.
2026-10-12 12:46:45 +03:00
andrey 0f42ecd0a8 Скорость: ряд кнопок 1×/2×/4×/8× вместо кнопки-цикла
- #speedRow с #speed1..#speed8, активная подсвечена; setSpeed(m) вместо cycleSpeed
- speedMult теперь 1/2/4/8 (транзиентный); документация и тесты обновлены
2026-10-12 11:43:11 +03:00
andrey b601bcb195 Кампания: 14 уровней (ручные + генератор) с новыми фигурами
Было 3 почти одинаковых уровня. Теперь первый — «Разминка» (блок),
второй — «Каравай», плюс «Рой» (много фигур) и «Издалека» (враг вне
зоны рисования игрока), и 10 сгенерированных уровней.

- Фигуры врага: блок, улей, мигалка, жаба, каравай, пульсар, ружьё
  Госпера; есть уровни с врагом вне зоны игрока.
- tools/gen-campaign-levels.mjs — детерминированный генератор (seeded
  PRNG, без Math.random); вывод вшит в CAMPAIGN_LEVELS статически, в
  рантайме ничего не генерируется.
- Решаемость намеренно не гарантируется: уровни с ружьём почти
  непроходимы — это задумка, а не дефект.
- Тесты обновлены под 14 уровней (416/416): инварианты вместо жёстких
  значений, проверка набора фигур, врага вне зоны, детерминизма
  генератора и отсутствия дрейфа между генератором и вшитым блоком.
- Ядро step не тронуто; паритет 273/273 сохранён.
2026-10-12 09:49:20 +03:00
andreyandopencode 046ab33308 Кампания: одиночные уровни с зачисткой врага
Новый режим (кнопка «Кампания» → экран уровней): на поле заранее
расставлены стабильные фигуры врага (цвет 2), игрок (цвет 1) должен
полностью убрать цвет врага только перекраской в симуляции. У каждого
уровня своя прямоугольная зона рисования и свой бюджет клеток.

- FSM переиспользует PLACE_P1/RUNNING/PAUSED/EDIT_P1/GAME_OVER, добавлено
  только LEVEL_SELECT; режим различается флагами campaign/levelIndex.
- Зона игрока — прямоугольник zoneRect, новый предикат inZoneAt;
  inZone/zoneSplit не менялись (контракт тестов).
- Клетки врага нельзя стирать/перекрашивать (в paintTool пропускаются
  во всех ветках без траты бюджета); вымирание игрока не проигрыш.
- Победа = 0 клеток врага (reason='win'), поражение = бюджет исчерпан и
  враг жив на авто-паузе (reason='defeat').
- Прогресс с разблокировкой уровней в localStorage (аддитивные поля
  сейва, SAVE_VERSION без изменений); выход в меню откладывает уровень
  в suspended.
- Настройки меню (menuFieldW/H/Wrap/Budget) отделены от активной игры —
  размер/бюджет уровня не утекают в обычную партию.
- Тесты: +76 кейсов кампании (408/408), паритет step сохранён 273/273.

Co-authored-by: opencode <noreply@opencode.ai>
2026-10-12 09:06:08 +03:00
andrey d7de48f77a Светлая тема: читаемый дропдаун (активная подсветка + нативные popup)
- color-scheme по темам: popup селектов/скроллбары/спиннеры следуют теме
- явные цвета option (var(--bg)/var(--fg)) — страховка для Firefox/Safari
- --hl-bg/--hl-fg для активных .toolbtn/#optPattern/.act.speed: в светлой
  теме был тёмный фон при тёмном тексте (контраст 8.3), в тёмной — без регрессии
2026-10-11 22:37:50 +03:00
andrey 3b4ee8ed2b Авто-финал при тупике: стабильное поле + у отстающего кончился бюджет 2026-10-11 22:35:31 +03:00
andrey 4d1f1dece5 Поворот паттерна колесом мыши (90° за щелчок)
- глобалы stampRot (0..3) и wheelAccum, хелперы rotate90/stampCells;
- обработчик wheel на canvas ({passive:false}): только в фазах рисования при
  активном штампе, deltaY копится до порога WHEEL_ROT_STEP (мышь — один поворот
  на щелчок, тачпад — контролируемо), deltaMode нормализуется к пикселям;
- paintTool и ghost-превью используют stampCells (поворот влияет и на
  постановку, и на превью), stampRot/wheelAccum сбрасываются при смене
  паттерна/инструмента;
- тесты (325/325): stampCells 0..3 и возврат формы, wheel вниз/вверх/круг,
  инертность без штампа и вне рисования, накопление мелких дельт, постановка
  повёрнутой фигуры, сброс поворота; доки.
2026-10-11 22:26:09 +03:00
peri4 881ec2205b Обновить index.html 2026-10-11 22:13:56 +03:00
andrey 20313ebf2d Готовые паттерны (штампы): глайдер, улей, ружьё Госпера и др.
- таблица PATTERNS (ASCII-формы) + patternCells/findPattern, глобал stamp
  (транзиентный, как speedMult — в сейв не пишется);
- ветка stamp в paintTool: якорь левый верхний угол, зона/бюджет как у краски,
  кламп по доске, обрезка по половине; одно нажатие = одна постановка;
- дропдаун #optPattern в тулбаре (подсветка .active, disabled вне рисования,
  selectTool сбрасывает штамп); ghost-превью с показом вылета за зону;
- тесты (312/312): разбор форм, постановка, зона/бюджет/кламп/Сброс, корректность
  фигур на симуляции, проводка DOM/ввода, транзиентность; доки.
2026-10-11 22:08:26 +03:00
andrey e6804e7300 Победа при вымирании: партия завершается сразу, как у игрока 0 клеток
checkEnd() больше не смотрит на бюджет: после каждого шага симуляции
исчезнувший цвет проигрывает немедленно (бюджет финал не отодвигает).
Обновлены тесты (фикстуры с обоими цветами, новый кейс финала при
непустом бюджете) и документация.
2026-10-11 21:43:19 +03:00
andrey 0851b689af Дорисовка — только на стабильном поле: авто-пауза при стабилизации
Симуляция сама встаёт на паузу, когда поле перестало меняться (фиксированная
точка или короткий цикл: хеш поколения повторяется в окне STABLE_WINDOW=32).
Признак fieldStable открывает дорисовку (EDIT_P1/EDIT_P2) только на «застывшем»
поле; на живом (глайдеры/рост/хаос) кнопки скрыты, партию завершают вручную.
fieldStable сохраняется в сейве (stable) и в отложенной партии (suspended).
2026-10-11 21:28:17 +03:00
andrey 19c02956af Правая кнопка мыши стирает клетки (ластик на время удержания) 2026-10-11 20:47:10 +03:00
andrey 54936feff9 Клавиатура: пробел ставит/снимает паузу (RUNNING↔PAUSED) 2026-10-11 19:59:01 +03:00
andrey de954c601c Скорость симуляции: кнопка-цикл ×1/×2/×4 в RUNNING 2026-10-11 15:04:32 +03:00
andrey b6667e6829 Favicon: инлайн SVG (глайдер) в data:URI, без внешних ассетов 2026-10-11 14:53:10 +03:00
andrey 2b87c00ddb Светлая/тёмная тема: переключатель в меню, canvas-палитра, сейв
- CSS-переменные структурных цветов UI + :root[data-theme=light]
- CANVAS_THEME (dark/light) и applyTheme/setTheme; draw читает цвета зон из COLORS
- селект #optTheme в меню; syncDom отражает тему
- поле theme аддитивно в сейв v2 (старые сейвы → dark)
- тесты темы + стабильные глобалы; AGENTS/README актуализированы
- кнопки «В меню»/«Завершить» перекрашены; 5% подсветка территорий в RUNNING/PAUSED
2026-10-11 14:46:20 +03:00
andrey 785b8b22f6 Подсветка зон и кнопок цветом игрока: тинт половины, рамка панели, оверлеи 2026-10-11 14:33:16 +03:00
andrey 2c05ab7ef1 Возврат P1↔P2 после расстановки и подтверждение завершения партии
- Первичная передача: после расстановки P2 новое состояние HANDOFF_P2_START
  (кнопки «Начать симуляцию» и «Вернуться к игроку 1»). handoffBack() ведёт
  P2→P1 с сохранением расстановки соперника (правая половина снова скрыта);
  «Готов» из PLACE_P2 больше не стартует симуляцию сразу.
- Снимок начала хода PLACE_* чистится по зоне игрока (enterPlace/clearZone), чтобы
  «Сброс»/ластик откатывали только расстановку текущего игрока и корректно
  возвращали бюджет, не трогая чужую половину.
- «Завершить партию» (#btnEnd/#btnEndPause) теперь открывает #confirm
  (confirmAction='end'), ставит симуляцию на паузу; «Отмена» возвращает RUNNING,
  подтверждение → GAME_OVER/manual. Единый confirmAction='new'|'end' поверх
  #btnConfirmOk/#btnConfirmCancel: партию нельзя потерять одним кликом.
- Финальный reason='manual' выставляется через requestEndGame→confirmProceed.
- Тесты 253/253, паритет step==clean 273/273 (+torus 273/273), check: OK.
2026-10-11 14:22:28 +03:00
andrey 87f8fd3195 Вымирание с оглядкой на бюджет; меню не теряет партию; раздельная дорисовка
- checkEnd(): вымирание объявляется только когда исчезнувший цвет уже не вернуть
  (у игрока без клеток остаток бюджета = 0); иначе партия продолжается до ручного финала.
- Кнопки «В меню»/⚙️ откладывают партию в suspended; в меню — «Продолжить партию»,
  «Играть»/«Реванш» при suspended спрашивают подтверждение (#confirm). suspended
  переживает reload (аддитивное поле сейва v2, без слома миграции).
- Дорисовка: отдельные «Дорисовать красным»/«Дорисовать зелёным» и «Старт симуляции»
  в PAUSED; убрана форс-цепочка EDIT_P1→EDIT_P2; состояния HANDOFF_E1_E2/HANDOFF_E2_RUN
  удалены; commitTurn() из EDIT_* → PAUSED.
- Первичная передача: кнопка «Вернуться к игроку 1» (handoffBack → PLACE_P1) с
  сохранением снимка начала хода P1.
- Тесты 234/234, паритет step==clean 273/273 (+torus 273/273), check: OK.
2026-10-11 14:04:27 +03:00
andrey b38dd6d4e5 Бюджет — комбобокс; прямоугольные поля 60×100/100×60 в дропдауне размера 2026-10-11 12:18:21 +03:00
andrey 3bdbecdd8a Компактное меню: дропдаун размера поля и свёрнутое описание «Как играть» 2026-10-11 12:09:00 +03:00
andrey e6bf151a87 Пауза инлайн без диалога + кнопка «Дорисовать клетки» рядом с «Паузой»
Модальный оверлей #pause удалён: кнопки паузы живут в нижней панели/боковой
зоне и переключаются по state. В RUNNING видны «Пауза», «Дорисовать клетки»
(рядом) и «Завершить партию»; в PAUSED — «Продолжить», «Дорисовать клетки»,
«Завершить партию» и «В меню». startEdit() работает из обеих фаз. Действия
переносятся (flex-wrap). Тесты раскладки/паузы (217/217).
2026-10-11 12:04:12 +03:00
andrey ebfea30279 Адаптивная раскладка: доска во всю высоту, управление в боковой панели
В ландшафте доска занимает всю высоту и прижата влево, а HUD/тулбар/действия
собираются в боковую панель справа (body.side, --side-w) и не пересекаются с
доской. В портрете/узком ландшафте — прежняя центрированная раскладка.
Добавлен глобал layoutSide и тесты раскладки (30).
2026-10-11 11:57:27 +03:00
andrey 36f0fa2623 R2-3: полное покрытие новых фич, паритет torus, полировка
- tests/test.js: 148 -> 206 проверок (разделы 23 wrap/torus, 24 зоны, 25 бюджет,
  26 размеры, 27 сейв v2/миграция, 28 рендер/resize 300x300, 29 HUD 381-400px);
  resetGlobals сбрасывает tool/reason/winner; sleep заменён на waitCond/settleViewport.
- tools/old-ref/: независимый torus-эталон (nextStepCleanWrap, CLI --torus,
  gen-torus.js, reference-torus.json); diff-our.js сверяет stepN при setWrap(true).
- index.html: медиазапрос HUD 380->400px (убрана микро-обрезка 381-388px);
  инлайн-комментарий про согласованность useWrap/setWrap.
- AGENTS.md: полный список контракта дополнен; useWrap-заметка уточнена; покрытие
  приведено в соответствие. README: скорость — константа в коде, не UI.
- .gitignore: tools/old-ref/.tmp-*.txt.
- Прогоны: check: OK; суита 206/206 (3 повтора); паритет clean 273/273 + torus 273/273.
- Ревью-гейт R2-3 (Oracle): PASS; minor/nit закрыты.
2026-10-11 11:02:23 +03:00
andrey d93fbbc393 R2-2: производительный рендер 300x300 + UI новых опций
- index.html: offscreen fieldCanvas (fieldW x fieldH, 1px = 1 клетка) + ImageData
  (rebuildFieldImage) + dirty-флаг renderDirty; draw использует drawImage со
  imageSmoothingEnabled=false; resize offscreen не пересобирает; сетка только
  при cs>=5 (на 300x300 скрыта).
- Подсветка зон рисования: неактивная половина затемняется оверлеем ПОД клетками
  + линия границы зоны; только в фазах рисования.
- HUD бюджета: #budgetLabel (N / total) цветом игрока, .zero при нуле; виден
  только в фазах рисования; сжат HUD + медиазапрос <=380px (фикс вёрстки M1).
- UI меню: сетка #sizeRow из 9 размеров (40..300, 3 столбца), чекбокс #optWrap
  ('Замкнутая карта') -> setWrap, ввод #optBudget -> setBudgetTotal + пресеты.
- stepN ставит renderDirty (m1); startRunning обнуляет turnStartBudget (m3);
  убран дублирующий rebuildNeighborTables (n1); README обновлён (m2).
- tests/test.js: +проверки вёрстки HUD (320/375/390) и stepN->renderDirty; 148/148.
- AGENTS.md: документация рендера/зон/HUD/UI; renderDirty, fieldCanvas в глобалах.
- Прогоны: check: OK; суита 148/148; паритет с clean 273/273.
- Гейт R2-2 (Oracle): правки (M1) -> fix-12; ре-ревью PASS.
2026-10-11 10:43:39 +03:00
andrey 5ff3dc0283 R2-1: модель и опции (wrap, размеры до 300, зоны, бюджет, сейв v2)
- index.html: опция 'Замкнутая карта' (wrap/torus) с таблицами индексов соседей
  (colL/colR/rowU/rowD, rebuildNeighborTables), useWrap в liveNeighbors/step/stepN
  (опционален, дефолт — глобал wrap=false); FIELD_SIZES до 300;
  зоны рисования inZone/zoneSplit (P1 лево / P2 право), paintTool как игровое
  действие (зона+бюджет), applyBrush остаётся геометрическим;
  бюджет клеток DEFAULT_BUDGET=100 (budget/remaining/setBudgetTotal, снимок
  turnStartBudget, списание на новые клетки, возврат ластиком только правок
  текущего хода, resetGame откатывает поле и бюджет);
  сейв v2 (SAVE_VERSION=2, ключ life-save-v1 исторический, миграция M1 v1->v2,
  wrap/budget в форме), троттлинг persist в RUNNING, turnStartField=null в startRunning.
- tests/test.js: список глобалов дополнен, v===2, резюм-R1 зона P2 перенесена.
- AGENTS.md: плоский Uint8Array; границы (дефолт жёсткие + опция wrap); размеры
  до 300; зоны; бюджет; форма сейва v2 + миграция; новые глобалы; подводные камни.
- Прогоны: check: OK; суита 123/123; паритет с clean 273/273 (wrap off).
- Ревью-гейт R2-1 (Oracle): PASS.
2026-10-11 10:23:11 +03:00
andrey 7d788404f8 Фаза 4: персистентность, полная суита и полировка
- index.html: реальная персистентность (loadSave/applySave/persist/freshSave,
  SAVE_VERSION=1, форма сейва v1, base64-поле, миграции), резюм незавершённой
  партии после reload, авто-пауза при visibilitychange без перезаписи сейва;
  полировка: modeForState вместо мёртвого mode, валидация FIELD_SIZES,
  setFieldSize игнорирует чужие значения, согласованный дефолт fieldH,
  сброс hoverCell на pointerup/cancel, актуализирован комментарий mode.
- tests/test.js: расширена 65 -> 123 проверок (резюм R1/R2/R3, миграция/битый
  сейв, настройки, resize, клики DOM-кнопок, applyIntent, паритет сохранён).
- tools/old-ref/: harness паритета перенесён в репозиторий (old_ref.cpp,
  reference*.json, генераторы, diff-our.js, ref-*.txt, ce1.txt, README).
- AGENTS.md/README.md: форма сейва, миграции, резюм, harness, структура.
- .gitignore: секрет .gitea_token и бинарники/логи harness.
- Прогоны: check: OK; npm test — ИТОГ 123/123; паритет clean 273/273.
- Ревью-гейт 4 (Oracle): PASS; minor/mit закрыты.
2026-10-11 09:29:20 +03:00
andrey 2705c757f8 Фаза 3: ядро игры (модель поля, симуляция, FSM, скрытие, UI)
- index.html: константы/данные, чистая модель поля (Uint8Array, liveNeighbors
  с жёсткими границами, majorityColor с tie-break, step/stepN без мутации src,
  applyBrush с клампом, countByPlayer, isExtinct, hashField), FSM из 11 состояний
  с переходами и reason/winner, visibleMask (скрытие только в PLACE_*),
  отрисовка по маске, Pointer Events с интерполяцией, цикл с клампом dt,
  HUD/тулбар/оверлеи, тест-форсы.
- Паритет с прототипом old доказан дифференциально: наш step == clean-эталон
  273/273; расхождения с proto только там, где proto != clean (баг parent[],
  не воспроизводим — решение пользователя: чистое большинство из 3).
- tests/test.js: раздел 16 паритета; tests/parity-fixture.json — фикстура.
- AGENTS.md: контракт глобалов дополнен (ctx/canvas/viewW/viewH/dpr/visibleMask/
  applyIntent/reason/STEPS_PER_SEC, tests/package-lock.json).
- .gitignore: исключён .gitea_token (гигиена секретов, finding M1 гейта 3).
- Прогоны: check: OK; npm test — ИТОГ 65/65, exit 0. Ревью-гейт 3 (Oracle): PASS.
2026-10-11 09:10:26 +03:00
andrey e889cf8e45 Фаза 2: инфраструктура разработки и тестирования
- tools/check.mjs: проверка синтаксиса JS из <script> в index.html через node --check
  (перевод номера строки в координаты index.html), exit-коды 0/1.
- tests/: каркас headless-суиты на puppeteer-core (ok/waitState/waitCond/sleep,
  viewport 390x844@2, контроль pageerror/console.error, ИТОГ N/N и exit code).
- index.html: минимальный скелет (canvas/DPR/resize, state=MENU, SAVE_KEY, 60x60)
  без игровой логики — она в фазе 3.
- Прогоны: check: OK; npm test — ИТОГ 12/12, exit 0.
- Ревью-гейт 2 (Oracle): PASS; minor/nit перенесены в фазу 3.
2026-10-11 08:47:11 +03:00