Initialize Project Sacrifice backlog and source baseline

This commit is contained in:
2026-08-18 00:46:42 +03:00
parent d28ea8c3fd
commit 29d4a2d8d8
34 changed files with 4630 additions and 151 deletions
+17
View File
@@ -0,0 +1,17 @@
{"id":"int-b6ed29b5","kind":"field_change","created_at":"2026-08-17T21:41:12.523341Z","actor":"E.Gavrilov","issue_id":"goon-game-character-basic","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Реализовано в текущем vertical slice."}}
{"id":"int-e1f7e935","kind":"field_change","created_at":"2026-08-17T21:41:13.724781Z","actor":"E.Gavrilov","issue_id":"goon-game-death-core","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Реализовано и проверено ботом уровня 5."}}
{"id":"int-20c8f120","kind":"field_change","created_at":"2026-08-17T21:41:14.909909Z","actor":"E.Gavrilov","issue_id":"goon-game-physics-basic","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Базовая физика присутствует; production determinism остаётся отдельной задачей."}}
{"id":"int-9bcb8032","kind":"field_change","created_at":"2026-08-17T21:41:16.087427Z","actor":"E.Gavrilov","issue_id":"goon-game-hazards-vslice","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Базовый набор опасностей присутствует в 10 уровнях."}}
{"id":"int-c455b57f","kind":"field_change","created_at":"2026-08-17T21:41:17.181815Z","actor":"E.Gavrilov","issue_id":"goon-game-puzzle-vslice","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Базовые кнопки, двери и платформы реализованы."}}
{"id":"int-19d2878b","kind":"field_change","created_at":"2026-08-17T21:41:18.38315Z","actor":"E.Gavrilov","issue_id":"goon-game-camera-basic","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Базовое сглаженное слежение реализовано."}}
{"id":"int-5cac5997","kind":"field_change","created_at":"2026-08-17T21:41:19.507761Z","actor":"E.Gavrilov","issue_id":"goon-game-levels-greybox","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Созданы 10 последовательных greybox-уровней."}}
{"id":"int-884a2d2b","kind":"field_change","created_at":"2026-08-17T21:41:20.690672Z","actor":"E.Gavrilov","issue_id":"goon-game-ui-vslice","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Базовый HUD и multitouch controls реализованы."}}
{"id":"int-2869dcd6","kind":"field_change","created_at":"2026-08-17T21:41:21.892414Z","actor":"E.Gavrilov","issue_id":"goon-game-save-basic","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Локальный прогресс и лучшие результаты сохраняются."}}
{"id":"int-ba732ef6","kind":"field_change","created_at":"2026-08-17T21:41:23.065757Z","actor":"E.Gavrilov","issue_id":"goon-game-android-apk","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"APK versionCode 3 собран, подписан, установлен и запущен."}}
{"id":"int-1f45058b","kind":"field_change","created_at":"2026-08-17T21:41:24.243761Z","actor":"E.Gavrilov","issue_id":"goon-game-unit-tests","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"8 unit-тестов и lintDebug прошли."}}
{"id":"int-681cf956","kind":"field_change","created_at":"2026-08-17T21:41:25.437459Z","actor":"E.Gavrilov","issue_id":"goon-game-level5-bot","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Android acceptance-тест уровня 5 проходит."}}
{"id":"int-756d475a","kind":"field_change","created_at":"2026-08-17T21:41:26.677404Z","actor":"E.Gavrilov","issue_id":"goon-game-milestone-prototype","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Exit criteria прототипа подтверждены 10 greybox-уровнями."}}
{"id":"int-5bdbc36c","kind":"field_change","created_at":"2026-08-17T21:41:27.805316Z","actor":"E.Gavrilov","issue_id":"goon-game-plan","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
{"id":"int-8a40cc5a","kind":"field_change","created_at":"2026-08-17T21:41:28.366857Z","actor":"E.Gavrilov","issue_id":"goon-game-e15","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
{"id":"int-70512c96","kind":"field_change","created_at":"2026-08-17T21:41:28.879205Z","actor":"E.Gavrilov","issue_id":"goon-game-e16","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
{"id":"int-b5f69bfd","kind":"field_change","created_at":"2026-08-17T21:41:29.38583Z","actor":"E.Gavrilov","issue_id":"goon-game-milestone-vslice","extra":{"field":"status","new_value":"in_progress","old_value":"open"}}
+68
View File
@@ -0,0 +1,68 @@
{"_type":"issue","id":"goon-game-milestone-vslice","title":"Milestone: Vertical Slice","description":"8–10 polished уровней, Android controls, camera, audio, UI, save и 5–6 mechanics.","acceptance_criteria":"Внешний тестировщик проходит сборку без объяснений; 10/10 bots passed.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:41:00Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:10Z","started_at":"2026-08-17T21:41:29Z","labels":["current","gate","milestone"],"dependencies":[{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-android-lifecycle","type":"blocks","created_at":"2026-08-18T00:44:22Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-art-vslice","type":"blocks","created_at":"2026-08-18T00:44:20Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-audio-vslice","type":"blocks","created_at":"2026-08-18T00:44:19Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-controls-settings","type":"blocks","created_at":"2026-08-18T00:44:17Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-e16","type":"parent-child","created_at":"2026-08-18T00:43:55Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-human-playtest","type":"blocks","created_at":"2026-08-18T00:44:16Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-menus-localization","type":"blocks","created_at":"2026-08-18T00:44:18Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-perf-budgets","type":"blocks","created_at":"2026-08-18T00:44:23Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-regression-10","type":"blocks","created_at":"2026-08-18T00:44:15Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-vslice","depends_on_id":"goon-game-save-versioning","type":"blocks","created_at":"2026-08-18T00:44:21Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":9,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"goon-game-milestone-prototype","title":"Milestone: Prototype","description":"Character, death, corpse, respawn, spikes, plate, door, goal и restart без финального art.","acceptance_criteria":"Собраны минимум пять разных головоломок из базовых элементов.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:40:58Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:01Z","closed_at":"2026-08-17T21:41:27Z","close_reason":"Exit criteria прототипа подтверждены 10 greybox-уровнями.","labels":["closed","milestone"],"dependencies":[{"issue_id":"goon-game-milestone-prototype","depends_on_id":"goon-game-e16","type":"parent-child","created_at":"2026-08-18T00:43:54Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-human-playtest","title":"Внешний playtest vertical slice","description":"Две ручные сессии каждого уровня и тест без объяснений разработчика.","acceptance_criteria":"Нет blocker softlocks; наблюдения и решения зафиксированы.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:54Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:54Z","labels":["qa","vertical-slice"],"dependencies":[{"issue_id":"goon-game-human-playtest","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:51Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-human-playtest","depends_on_id":"goon-game-level-balance","type":"blocks","created_at":"2026-08-18T00:41:36Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-regression-10","title":"Единый playability regression 10/10","description":"Запускать всех ботов одной Gradle-командой и публиковать краткий отчёт по уровням.","acceptance_criteria":"Три полных последовательных прогона дают 10/10 passed менее чем за 5 минут каждый.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":480,"created_at":"2026-08-17T21:40:51Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:51Z","labels":["current","gate","qa"],"dependencies":[{"issue_id":"goon-game-regression-10","depends_on_id":"goon-game-bots-1-4","type":"blocks","created_at":"2026-08-18T00:41:33Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-regression-10","depends_on_id":"goon-game-bots-6-10","type":"blocks","created_at":"2026-08-18T00:41:34Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-regression-10","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:50Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"goon-game-bots-6-10","title":"Добавить ботов уровней 6–10","description":"Доказать freeze, electricity, timed platform, multi-signal и mastery level.","acceptance_criteria":"Уровни 6–10 проходят по три раза подряд без retries.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:49Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:49Z","labels":["bot","current","qa"],"dependencies":[{"issue_id":"goon-game-bots-6-10","depends_on_id":"goon-game-bot-framework","type":"blocks","created_at":"2026-08-18T00:41:32Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-bots-6-10","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:49Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-bots-1-4","title":"Добавить ботов уровней 1–4","description":"Доказать movement/jump, spikes/corpses, pressure door и moving lift реальными inputs.","acceptance_criteria":"Уровни 1–4 проходят по три раза подряд без retries.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:47Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:47Z","labels":["bot","current","qa"],"dependencies":[{"issue_id":"goon-game-bots-1-4","depends_on_id":"goon-game-bot-framework","type":"blocks","created_at":"2026-08-18T00:41:30Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-bots-1-4","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:48Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-bot-framework","title":"Обобщить acceptance bot framework","description":"Выделить сценарные шаги, snapshots, диагностику таймаутов и общий runner для уровней 1–10.","acceptance_criteria":"Новый уровень подключается декларативным сценарием; ошибка показывает последний шаг и state.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:45Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:45Z","labels":["bot","current","qa"],"dependencies":[{"issue_id":"goon-game-bot-framework","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:47Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"goon-game-level5-bot","title":"Acceptance-бот уровня 5","description":"Бот использует production GameView inputs, одну жертву, тело, плиту, дверь и goal.","acceptance_criteria":"Инструментальный тест проходит на Android 16 примерно за 8,2 секунды.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:40:42Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:25Z","closed_at":"2026-08-17T21:41:25Z","close_reason":"Android acceptance-тест уровня 5 проходит.","labels":["baseline","bot","closed"],"dependencies":[{"issue_id":"goon-game-level5-bot","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:46Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-unit-tests","title":"Базовые unit tests и Android Lint","description":"В текущем проекте проходят 8 unit-тестов и lintDebug.","acceptance_criteria":"Чистая Gradle-сборка зелёная.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:40:41Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:24Z","closed_at":"2026-08-17T21:41:24Z","close_reason":"8 unit-тестов и lintDebug прошли.","labels":["baseline","closed","tests"],"dependencies":[{"issue_id":"goon-game-unit-tests","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:44Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-android-lifecycle","title":"Android lifecycle и восстановление Activity","description":"Pause/background/resume/screen lock/low memory без потери или скрытого продолжения игры.","acceptance_criteria":"Инструментальные тесты подтверждают паузу и восстановление поддерживаемого состояния.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:26Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:26Z","labels":["android","current"],"dependencies":[{"issue_id":"goon-game-android-lifecycle","depends_on_id":"goon-game-e12","type":"parent-child","created_at":"2026-08-18T00:43:36Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"goon-game-android-apk","title":"Собираемый и устанавливаемый Android APK","description":"Debug APK versionCode 3 проверен на Android 16, ZIP и подписи валидны.","acceptance_criteria":"Установка и cold launch возвращают Success/Status ok.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:40:24Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:23Z","closed_at":"2026-08-17T21:41:23Z","close_reason":"APK versionCode 3 собран, подписан, установлен и запущен.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-android-apk","depends_on_id":"goon-game-e12","type":"parent-child","created_at":"2026-08-18T00:43:35Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-level-balance","title":"Исправить стабильность и баланс уровней 1–10","description":"Устранить softlocks, нестабильные тела и чрезмерно точные прыжки, найденные ботами и ручным тестированием.","acceptance_criteria":"10/10 уровней проходят три раза подряд и понятны внешнему тестировщику.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:39:56Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:56Z","labels":["balance","current","levels"],"dependencies":[{"issue_id":"goon-game-level-balance","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T00:43:20Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-level-balance","depends_on_id":"goon-game-regression-10","type":"blocks","created_at":"2026-08-18T00:41:35Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-level-balance","depends_on_id":"goon-game-solution-graphs","type":"blocks","created_at":"2026-08-18T00:41:35Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-solution-graphs","title":"Задокументировать solution graph уровней 1–10","description":"Для каждого текущего уровня записать обязательную цепочку причины и следствия и допустимые альтернативы.","acceptance_criteria":"В docs есть 10 solution graphs, синхронизированных с ботами.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:39:54Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:54Z","labels":["current","design","levels"],"dependencies":[{"issue_id":"goon-game-solution-graphs","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T00:43:19Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-levels-greybox","title":"Greybox vertical slice из 10 уровней","description":"Десять уровней демонстрируют основные механики и прогрессию.","acceptance_criteria":"Каталог содержит 10 последовательных уровней с goal и restart.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:52Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:19Z","closed_at":"2026-08-17T21:41:19Z","close_reason":"Созданы 10 последовательных greybox-уровней.","labels":["baseline","closed","vertical-slice"],"dependencies":[{"issue_id":"goon-game-levels-greybox","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T00:43:17Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-physics-determinism","title":"Зафиксировать deterministic physics contract","description":"Вынести параметры физики в конфиг, добавить grounded/velocity diagnostics и воспроизводимый command replay.","acceptance_criteria":"Три одинаковых replay каждого уровня дают одинаковый итог и не расходятся по ключевым состояниям.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:39:33Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:33Z","labels":["current","physics"],"dependencies":[{"issue_id":"goon-game-physics-determinism","depends_on_id":"goon-game-e03","type":"parent-child","created_at":"2026-08-18T00:43:08Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-physics-basic","title":"Базовая предсказуемая Canvas-физика","description":"Коллизии персонажа, тел, платформ и дверей работают в текущем vertical slice.","acceptance_criteria":"Unit-тесты и бот уровня 5 проходят.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:31Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:15Z","closed_at":"2026-08-17T21:41:15Z","close_reason":"Базовая физика присутствует; production determinism остаётся отдельной задачей.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-physics-basic","depends_on_id":"goon-game-e03","type":"parent-child","created_at":"2026-08-18T00:43:07Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-death-core","title":"Death, corpse и respawn без сброса уровня","description":"Работающая основа смерти, сохранения тела и появления следующего персонажа.","acceptance_criteria":"Проверено на уровне 5 и smoke-тестах APK.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:22Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:14Z","closed_at":"2026-08-17T21:41:14Z","close_reason":"Реализовано и проверено ботом уровня 5.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-death-core","depends_on_id":"goon-game-e02","type":"parent-child","created_at":"2026-08-18T00:43:03Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e16","title":"EPIC-16 Milestones \u0026 Release","description":"Prototype → Vertical Slice → Production → Alpha → Beta → Release Candidate. Разделы 53–59.","acceptance_criteria":"Есть подписанный store build без blocker и critical progression bugs.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:16Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:10Z","started_at":"2026-08-17T21:41:29Z","labels":["epic","milestone","release"],"dependencies":[{"issue_id":"goon-game-e16","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:43:00Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e15","title":"EPIC-15 QA","description":"Unit, integration, playability bots, softlock и mobile QA. Разделы 48–51, 63–64.","acceptance_criteria":"Все уровни автоматически и вручную проверены по обязательной матрице.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:14Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:09Z","started_at":"2026-08-17T21:41:28Z","labels":["current","epic","qa"],"dependencies":[{"issue_id":"goon-game-e15","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:58Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e12","title":"EPIC-12 Android","description":"Lifecycle, устройства, aspect ratios, graphics profiles и сборки. Разделы 41, 44–46, 49.","acceptance_criteria":"Release build стабильно работает на целевой Android-матрице.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:08Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:08Z","labels":["android","epic"],"dependencies":[{"issue_id":"goon-game-e12","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:55Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e07","title":"EPIC-07 Levels","description":"Solution graphs, greybox, прогрессия механик и производство 55–70 уровней. Разделы 19–25, 52–53, 66.","acceptance_criteria":"Каждый уровень имеет доказанное решение, DoD и место в content matrix.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:57Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:57Z","labels":["content","epic","levels"],"dependencies":[{"issue_id":"goon-game-e07","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:49Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e03","title":"EPIC-03 Physics","description":"Предсказуемая гибридная физика и бюджеты активных объектов. Разделы 15, 41–43, 65.","acceptance_criteria":"Одинаковая последовательность ввода воспроизводимо приводит к одинаковому состоянию головоломки.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:49Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:49Z","labels":["epic","physics"],"dependencies":[{"issue_id":"goon-game-e03","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:44Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e02","title":"EPIC-02 Death \u0026 Corpse","description":"Смерть как игровая механика, состояния тела, CorpseManager и respawn. Разделы 4–8.","acceptance_criteria":"Все типы смерти создают предсказуемое и пригодное для головоломки состояние тела.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:47Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:47Z","labels":["corpse","death","epic"],"dependencies":[{"issue_id":"goon-game-e02","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:43Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-plan","title":"Project Sacrifice: выполнить утверждённый production plan","description":"Главный bead для плана из docs/DEVELOPMENT_PLAN.md: vertical slice, production, alpha, beta и release candidate.","acceptance_criteria":"Release Candidate удовлетворяет критериям раздела 59; все дочерние эпики завершены либо явно исключены решением владельца продукта.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:23Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:08Z","started_at":"2026-08-17T21:41:28Z","labels":["gdd","master","production","tdd"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-mobile-qa","title":"Mobile QA matrix","description":"GPU, API, RAM, refresh rate, thermal, background, battery и controller проверки.","acceptance_criteria":"Матрица из раздела 49 заполнена и приложена к release evidence.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:40:56Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:56Z","labels":["android","qa"],"dependencies":[{"issue_id":"goon-game-mobile-qa","depends_on_id":"goon-game-device-matrix","type":"blocks","created_at":"2026-08-18T00:41:39Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-mobile-qa","depends_on_id":"goon-game-e15","type":"parent-child","created_at":"2026-08-18T00:43:53Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-crash-reporting","title":"Crash/ANR reporting без персональных данных","description":"Подключить отчёты ошибок с build/level context и политикой приватности.","acceptance_criteria":"Тестовая ошибка видна в системе; payload не содержит пользовательских секретов.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:38Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:38Z","labels":["analytics","android"],"dependencies":[{"issue_id":"goon-game-crash-reporting","depends_on_id":"goon-game-e14","type":"parent-child","created_at":"2026-08-18T00:43:43Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-corpse-sleep-pooling","title":"Sleeping corpses и object pooling","description":"Усыплять остановившиеся тела и pool-ить FX/audio/projectiles без изменения головоломок.","acceptance_criteria":"Нет GC spikes; спящие тела корректно пробуждаются внешним воздействием.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:40:34Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:34Z","labels":["corpse","performance"],"dependencies":[{"issue_id":"goon-game-corpse-sleep-pooling","depends_on_id":"goon-game-corpse-manager","type":"blocks","created_at":"2026-08-18T00:41:39Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-corpse-sleep-pooling","depends_on_id":"goon-game-e13","type":"parent-child","created_at":"2026-08-18T00:43:41Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-corpse-sleep-pooling","depends_on_id":"goon-game-perf-budgets","type":"blocks","created_at":"2026-08-18T00:41:40Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-perf-budgets","title":"Зафиксировать performance/physics budgets","description":"Определить бюджеты frame time, bodies, collision pairs, particles, draw calls и memory.","acceptance_criteria":"Метрики собираются воспроизводимо, целевые устройства держат согласованный FPS.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:32Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:32Z","labels":["performance","vertical-slice"],"dependencies":[{"issue_id":"goon-game-perf-budgets","depends_on_id":"goon-game-e13","type":"parent-child","created_at":"2026-08-18T00:43:39Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":3,"comment_count":0}
{"_type":"issue","id":"goon-game-release-build","title":"Release signing, APK и AAB","description":"Настроить release application ID, защищённый keystore и Google Play Internal Testing bundle.","acceptance_criteria":"Подписанные APK/AAB устанавливаются и проходят smoke-тест; секреты не в репозитории.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:30Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:30Z","labels":["android","release"],"dependencies":[{"issue_id":"goon-game-release-build","depends_on_id":"goon-game-e12","type":"parent-child","created_at":"2026-08-18T00:43:38Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-device-matrix","title":"Прогон Android 8–16 и форм-факторов","description":"Проверить API, RAM 2–4 GB, aspect ratios, refresh rates и interruptions.","acceptance_criteria":"Матрица устройств заполнена, blocker/critical дефекты закрыты.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:40:28Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:28Z","labels":["android","qa"],"dependencies":[{"issue_id":"goon-game-device-matrix","depends_on_id":"goon-game-android-lifecycle","type":"blocks","created_at":"2026-08-18T00:41:37Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-device-matrix","depends_on_id":"goon-game-e12","type":"parent-child","created_at":"2026-08-18T00:43:37Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-device-matrix","depends_on_id":"goon-game-perf-budgets","type":"blocks","created_at":"2026-08-18T00:41:38Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"goon-game-art-vslice","title":"Финальный art/animation pass уровней 1–10","description":"Заменить prototype Canvas-образы финальными персонажами, окружением, UI и анимациями.","acceptance_criteria":"Все игровые состояния читаются визуально; vertical slice готов внешнему тестировщику.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":4800,"created_at":"2026-08-17T21:40:19Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:19Z","labels":["art","vertical-slice"],"dependencies":[{"issue_id":"goon-game-art-vslice","depends_on_id":"goon-game-art-bible","type":"blocks","created_at":"2026-08-18T00:41:37Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-art-vslice","depends_on_id":"goon-game-e11","type":"parent-child","created_at":"2026-08-18T00:43:32Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-art-bible","title":"Утвердить самостоятельный Art Bible","description":"Зафиксировать stylized fantasy slapstick, требования к ассетам и границы относительно коммерческого референса.","acceptance_criteria":"Art Bible согласован и не требует копирования защищённого контента.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:17Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:17Z","labels":["art","design","vertical-slice"],"dependencies":[{"issue_id":"goon-game-art-bible","depends_on_id":"goon-game-e11","type":"parent-child","created_at":"2026-08-18T00:43:31Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-audio-vslice","title":"Audio pass vertical slice","description":"Добавить UI, movement, mechanisms, deaths, voice variations и музыку без немедленных повторов.","acceptance_criteria":"Все 10 уровней имеют настроенный звук и отдельные volume controls.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":2400,"created_at":"2026-08-17T21:40:15Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:15Z","labels":["audio","vertical-slice"],"dependencies":[{"issue_id":"goon-game-audio-vslice","depends_on_id":"goon-game-e10","type":"parent-child","created_at":"2026-08-18T00:43:30Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-save-versioning","title":"Версионирование и миграции сохранений","description":"Добавить saveVersion, миграции и тест обновления поверх предыдущего APK.","acceptance_criteria":"Старое сохранение мигрирует без потери завершённых уровней.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:11Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:11Z","labels":["save","vertical-slice"],"dependencies":[{"issue_id":"goon-game-save-versioning","depends_on_id":"goon-game-e09","type":"parent-child","created_at":"2026-08-18T00:43:27Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-save-basic","title":"Базовое сохранение прогресса","description":"Сохраняются unlock, best time и минимальное число жертв.","acceptance_criteria":"Прогресс переживает перезапуск приложения.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:40:09Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:22Z","closed_at":"2026-08-17T21:41:22Z","close_reason":"Локальный прогресс и лучшие результаты сохраняются.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-save-basic","depends_on_id":"goon-game-e09","type":"parent-child","created_at":"2026-08-18T00:43:26Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-accessibility","title":"Accessibility pass","description":"Проверить контраст, масштаб текста, safe areas, режим без вибрации и не-звуковые сигналы.","acceptance_criteria":"Критическая информация доступна визуально, элементы не перекрываются.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:06Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:06Z","labels":["accessibility","ui"],"dependencies":[{"issue_id":"goon-game-accessibility","depends_on_id":"goon-game-e08","type":"parent-child","created_at":"2026-08-18T00:43:25Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-menus-localization","title":"Меню, результаты и RU/EN локализация","description":"Добавить main/world/level select, settings, statistics, credits и полноценный result screen.","acceptance_criteria":"Все строки локализованы; игра проходится без объяснений разработчика.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:40:04Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:04Z","labels":["localization","ui","vertical-slice"],"dependencies":[{"issue_id":"goon-game-menus-localization","depends_on_id":"goon-game-e08","type":"parent-child","created_at":"2026-08-18T00:43:24Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-controls-settings","title":"Настройки размера, opacity и вибрации controls","description":"Дать игроку настройку touch controls и исключить случайные повторные прыжки.","acceptance_criteria":"Настройки сохраняются и проверены на 16:9, 20:9 и планшете.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:02Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:02Z","labels":["current","ui","vertical-slice"],"dependencies":[{"issue_id":"goon-game-controls-settings","depends_on_id":"goon-game-e08","type":"parent-child","created_at":"2026-08-18T00:43:23Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-ui-vslice","title":"Базовый HUD и multitouch controls","description":"В APK есть направления, jump, restart, pause, deaths и timer.","acceptance_criteria":"Управление работает одновременно двумя касаниями.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:40:00Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:21Z","closed_at":"2026-08-17T21:41:21Z","close_reason":"Базовый HUD и multitouch controls реализованы.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-ui-vslice","depends_on_id":"goon-game-e08","type":"parent-child","created_at":"2026-08-18T00:43:22Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-camera-polish","title":"Доработать production camera controller","description":"Добавить vertical damping, room transitions, focus target и управляемый shake.","acceptance_criteria":"Камера не скрывает обязательные объекты и не вызывает дискомфорт.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:39:50Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:50Z","labels":["camera","vertical-slice"],"dependencies":[{"issue_id":"goon-game-camera-polish","depends_on_id":"goon-game-e06","type":"parent-child","created_at":"2026-08-18T00:43:16Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-camera-basic","title":"Базовая камера с look-ahead и bounds","description":"В текущем runtime реализовано сглаженное горизонтальное слежение.","acceptance_criteria":"Камера работает во всех 10 уровнях.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:45Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:18Z","closed_at":"2026-08-17T21:41:18Z","close_reason":"Базовое сглаженное слежение реализовано.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-camera-basic","depends_on_id":"goon-game-e06","type":"parent-child","created_at":"2026-08-18T00:43:15Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-puzzle-events","title":"Выделить event-driven PuzzleSystem","description":"Заменить прямую проверку сигналов универсальными каналами событий и явными состояниями DoorController.","acceptance_criteria":"Кнопки, двери и платформы связываются данными; integration-тесты проходят.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:39:41Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:41Z","labels":["architecture","puzzle"],"dependencies":[{"issue_id":"goon-game-puzzle-events","depends_on_id":"goon-game-e05","type":"parent-child","created_at":"2026-08-18T00:43:13Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-puzzle-vslice","title":"Базовые puzzle objects vertical slice","description":"Работают pressure/toggle/timed switches, doors и moving platforms.","acceptance_criteria":"Существующие уровни используют все базовые типы.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:39Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:17Z","closed_at":"2026-08-17T21:41:17Z","close_reason":"Базовые кнопки, двери и платформы реализованы.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-puzzle-vslice","depends_on_id":"goon-game-e05","type":"parent-child","created_at":"2026-08-18T00:43:11Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-hazards-vslice","title":"Опасности vertical slice","description":"Реализованы spikes, saw, pit, freeze и electricity.","acceptance_criteria":"Опасности присутствуют в каталоге 10 уровней и не ломают restart.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:35Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:16Z","closed_at":"2026-08-17T21:41:16Z","close_reason":"Базовый набор опасностей присутствует в 10 уровнях.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-hazards-vslice","depends_on_id":"goon-game-e04","type":"parent-child","created_at":"2026-08-18T00:43:09Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-corpse-manager","title":"Выделить полноценный CorpseManager","description":"Убрать управление телами из монолитного GameView; добавить register/remove/transform/reset, лимит и диагностические счётчики.","acceptance_criteria":"Система имеет отдельный контракт, тесты и не меняет решения существующих уровней.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:39:27Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:27Z","labels":["architecture","corpse"],"dependencies":[{"issue_id":"goon-game-corpse-manager","depends_on_id":"goon-game-e02","type":"parent-child","created_at":"2026-08-18T00:43:04Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-character-states","title":"Завершить state machine персонажа","description":"Добавить явные Interact, Stunned, Turn, Land и animation-state переходы; вынести coyote/input buffer в конфиг.","acceptance_criteria":"Все переходы детерминированы, визуально различимы и имеют тесты.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:39:20Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:20Z","labels":["character","future"],"dependencies":[{"issue_id":"goon-game-character-states","depends_on_id":"goon-game-e01","type":"parent-child","created_at":"2026-08-18T00:43:02Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-character-basic","title":"Базовый character controller","description":"Движение, остановка, разворот, прыжок, падение и приземление в текущем Canvas runtime.","acceptance_criteria":"Функции присутствуют в APK и покрыты smoke-тестом.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":1,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:18Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:41:12Z","closed_at":"2026-08-17T21:41:12Z","close_reason":"Реализовано в текущем vertical slice.","labels":["baseline","closed"],"dependencies":[{"issue_id":"goon-game-character-basic","depends_on_id":"goon-game-e01","type":"parent-child","created_at":"2026-08-18T00:43:01Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e13","title":"EPIC-13 Optimization","description":"60 FPS target, object pooling, physics budgets и low profile. Разделы 41–44.","acceptance_criteria":"Производительность укладывается в зафиксированные бюджеты на средних и слабых устройствах.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:10Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:10Z","labels":["epic","performance"],"dependencies":[{"issue_id":"goon-game-e13","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:56Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e09","title":"EPIC-09 Save \u0026 Progression","description":"Версионированные сохранения, unlock, challenges, collectibles и статистика. Разделы 20–22, 34.","acceptance_criteria":"Прогресс сохраняется между версиями и корректно мигрирует при обновлении.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:01Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:01Z","labels":["epic","progression","save"],"dependencies":[{"issue_id":"goon-game-e09","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:51Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e08","title":"EPIC-08 UI/UX","description":"Android controls, HUD, меню, выбор уровней и доступность. Разделы 16–17, 31–33, 45.","acceptance_criteria":"Игра полностью понятна и управляема на поддерживаемых экранах без внешних объяснений.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:59Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:59Z","labels":["epic","ui","ux"],"dependencies":[{"issue_id":"goon-game-e08","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:50Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e06","title":"EPIC-06 Camera","description":"Следящая камера, look-ahead, damping, bounds и focus targets. Раздел 18.","acceptance_criteria":"Камера показывает будущий маршрут, не дёргается и соблюдает границы уровня.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:55Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:55Z","labels":["camera","epic"],"dependencies":[{"issue_id":"goon-game-e06","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:48Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e05","title":"EPIC-05 Puzzle Objects","description":"Кнопки, двери, платформы, весовые объекты и событийные сигналы. Разделы 11–14, 37.","acceptance_criteria":"Механизмы не связаны жёсткими ссылками и корректно работают с живым персонажем и телами.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:53Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:53Z","labels":["epic","puzzle"],"dependencies":[{"issue_id":"goon-game-e05","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:47Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e04","title":"EPIC-04 Hazards","description":"Каталог опасностей и универсальный контракт источника смерти. Разделы 5, 10, 38.","acceptance_criteria":"Опасности data-driven, задокументированы и проходят Android/QA проверки.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:51Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:51Z","labels":["epic","hazards"],"dependencies":[{"issue_id":"goon-game-e04","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:45Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e01","title":"EPIC-01 Character","description":"Персонаж, state machine, движение и platforming. Разделы 3, 17, 28, 36.","acceptance_criteria":"Персонаж соответствует утверждённому набору состояний и стабильно управляется на Android.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":1,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:38:45Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:38:45Z","labels":["character","epic"],"dependencies":[{"issue_id":"goon-game-e01","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:42Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-milestone-alpha","title":"Milestone: Alpha","description":"Вся игра проходима от первого меню до финала; контент существует.","acceptance_criteria":"End-to-end прохождение завершено на release-like build.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:41:05Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:02Z","labels":["alpha","milestone"],"dependencies":[{"issue_id":"goon-game-milestone-alpha","depends_on_id":"goon-game-crash-reporting","type":"blocks","created_at":"2026-08-18T00:44:28Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-alpha","depends_on_id":"goon-game-device-matrix","type":"blocks","created_at":"2026-08-18T00:44:27Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-alpha","depends_on_id":"goon-game-e16","type":"parent-child","created_at":"2026-08-18T00:43:57Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-alpha","depends_on_id":"goon-game-milestone-production","type":"blocks","created_at":"2026-08-18T00:44:26Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":3,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-milestone-production","title":"Milestone: Production","description":"Массовое производство уровней параллельными потоками после утверждения vertical slice.","acceptance_criteria":"Полный content matrix реализован и прошёл content pipeline.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:41:02Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:02Z","labels":["milestone","production"],"dependencies":[{"issue_id":"goon-game-milestone-production","depends_on_id":"goon-game-content-worlds","type":"blocks","created_at":"2026-08-18T00:44:25Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-production","depends_on_id":"goon-game-e16","type":"parent-child","created_at":"2026-08-18T00:43:56Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-production","depends_on_id":"goon-game-milestone-vslice","type":"blocks","created_at":"2026-08-18T00:44:24Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-analytics-contract","title":"Схема gameplay analytics","description":"Версионировать level_start/restart/death/complete/abandon/challenge и heatmap payload.","acceptance_criteria":"События документированы, минимальны и проверены на тестовом backend.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:36Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:36Z","labels":["analytics","production"],"dependencies":[{"issue_id":"goon-game-analytics-contract","depends_on_id":"goon-game-e14","type":"parent-child","created_at":"2026-08-18T00:43:42Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-modular-characters","title":"Модульные персонажи и косметика","description":"Body/helmet/face/weapon/cape/accessory, случайные имена и 30+ косметических элементов.","acceptance_criteria":"Новые жертвы визуально различаются, комбинации не ломают анимации.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":2880,"created_at":"2026-08-17T21:40:21Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:21Z","labels":["art","production"],"dependencies":[{"issue_id":"goon-game-modular-characters","depends_on_id":"goon-game-e11","type":"parent-child","created_at":"2026-08-18T00:43:33Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-challenges","title":"Challenges, collectibles и статистика","description":"Реализовать casualty/time/collectible цели и отображение трёх звёзд.","acceptance_criteria":"Расчёт покрыт unit-тестами и сохраняется по LevelID.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:13Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:40:13Z","labels":["production","progression"],"dependencies":[{"issue_id":"goon-game-challenges","depends_on_id":"goon-game-e09","type":"parent-child","created_at":"2026-08-18T00:43:29Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-content-worlds","title":"Спроектировать и произвести 4 мира / 55–70 уровней","description":"Content matrix четырёх миров, challenge-уровней и финальных испытаний по разделам 2 и 23.","acceptance_criteria":"Все уровни проходят content pipeline и Definition of Done.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:58Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:01Z","labels":["future","levels","production"],"dependencies":[{"issue_id":"goon-game-content-worlds","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T00:43:21Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-content-worlds","depends_on_id":"goon-game-milestone-vslice","type":"blocks","created_at":"2026-08-18T00:41:41Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"goon-game-weighted-objects","title":"Добавить общий IWeightedObject и физические объекты","description":"Добавить box, rock, ball, ice block и counterweight поверх единого контракта массы.","acceptance_criteria":"Pressure plate одинаково работает с персонажем, телом и объектами.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:39:43Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:43Z","labels":["production","puzzle"],"dependencies":[{"issue_id":"goon-game-weighted-objects","depends_on_id":"goon-game-e05","type":"parent-child","created_at":"2026-08-18T00:43:14Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-hazards-full","title":"Дополнить production-каталог опасностей","description":"Добавить crusher, fire, projectile, explosion и data-driven настройки импульса/звука/визуала.","acceptance_criteria":"Каждая опасность имеет prefab/data definition, тесты и минимум один учебный уровень.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":2400,"created_at":"2026-08-17T21:39:37Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:37Z","labels":["hazards","production"],"dependencies":[{"issue_id":"goon-game-hazards-full","depends_on_id":"goon-game-e04","type":"parent-child","created_at":"2026-08-18T00:43:10Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-corpse-states","title":"Расширить состояния тел","description":"Добавить crushed, burned и остальные утверждённые состояния поверх normal/pinned/frozen/conductive.","acceptance_criteria":"Каждое состояние используется минимум одной уникальной головоломкой и проходит DoD механики.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"feature","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:39:29Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:29Z","labels":["corpse","production"],"dependencies":[{"issue_id":"goon-game-corpse-states","depends_on_id":"goon-game-e02","type":"parent-child","created_at":"2026-08-18T00:43:05Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e14","title":"EPIC-14 Analytics","description":"События прохождения, позиции смертей и crash reporting без лишних персональных данных. Раздел 47.","acceptance_criteria":"События и ошибки проверены в тестовом канале, схема версионирована.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:12Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:12Z","labels":["analytics","epic"],"dependencies":[{"issue_id":"goon-game-e14","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:57Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e11","title":"EPIC-11 Art","description":"Самостоятельное художественное направление, модульные персонажи и анимации. Разделы 26–28.","acceptance_criteria":"Визуальный язык утверждён, оригинален и покрывает все игровые состояния.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:05Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:05Z","labels":["art","epic"],"dependencies":[{"issue_id":"goon-game-e11","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:54Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-e10","title":"EPIC-10 Audio","description":"Sound design, вариативность реакций и музыкальные loops. Разделы 29–30.","acceptance_criteria":"Vertical slice имеет полный audio pass без раздражающих повторов.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":2,"issue_type":"epic","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:39:03Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:39:03Z","labels":["audio","epic"],"dependencies":[{"issue_id":"goon-game-e10","depends_on_id":"goon-game-plan","type":"parent-child","created_at":"2026-08-18T00:42:52Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-milestone-rc","title":"Milestone: Release Candidate","description":"Финальный gate раздела 59.","acceptance_criteria":"0 blocker, 0 critical progression bugs, all levels completable, save migration/lifecycle/store signing/analytics/crash reporting verified.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":3,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:41:11Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:02Z","labels":["milestone","release-candidate"],"dependencies":[{"issue_id":"goon-game-milestone-rc","depends_on_id":"goon-game-analytics-contract","type":"blocks","created_at":"2026-08-18T00:44:32Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-rc","depends_on_id":"goon-game-e16","type":"parent-child","created_at":"2026-08-18T00:44:00Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-rc","depends_on_id":"goon-game-milestone-beta","type":"blocks","created_at":"2026-08-18T00:44:30Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-rc","depends_on_id":"goon-game-release-build","type":"blocks","created_at":"2026-08-18T00:44:31Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":3,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"goon-game-milestone-beta","title":"Milestone: Beta","description":"Feature freeze, зафиксированные уровни, bug fixing, optimization и localization QA.","acceptance_criteria":"Нет новых механик; beta QA matrix зелёная.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"open","priority":3,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T21:41:06Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T21:44:02Z","labels":["beta","milestone"],"dependencies":[{"issue_id":"goon-game-milestone-beta","depends_on_id":"goon-game-e16","type":"parent-child","created_at":"2026-08-18T00:43:58Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-milestone-beta","depends_on_id":"goon-game-milestone-alpha","type":"blocks","created_at":"2026-08-18T00:44:29Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
+1 -1
View File
@@ -5,7 +5,7 @@ local.properties
*.iml
captures/
# Beads / Dolt files (added by bd init)
# Beads / Dolt local runtime
.dolt/
*.db
.beads-credential-key
+27 -82
View File
@@ -1,96 +1,41 @@
# Agent Instructions
# Project instructions
This project uses **bd** (beads) for issue tracking. Run `bd prime` for full workflow context.
## Beads
> **Architecture in one line:** Issues live in a local Dolt database
> (`.beads/dolt/`); cross-machine sync uses `bd dolt push/pull` (a
> git-compatible protocol), stored under `refs/dolt/data` on your git
> remote — separate from `refs/heads/*` where your code lives.
> `.beads/issues.jsonl` is a passive export, not the wire protocol.
>
> See [SYNC_CONCEPTS.md](https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md)
> for the one-screen overview and anti-patterns (don't treat JSONL as the
> source of truth; don't `bd import` during normal operation; don't
> reach for third-party Dolt hosting before trying the default).
This project uses `bd` (Beads) as the source of truth for work tracking.
## Quick Reference
At the beginning of a work session run:
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work atomically
bd close <id> # Complete work
bd dolt push # Push beads data to remote
```sh
bd prime
bd status
bd ready --exclude-type epic
```
## Non-Interactive Shell Commands
Claim the selected bead before changing code and close it only after its acceptance criteria and relevant checks pass. Create follow-up beads for newly discovered work and connect real blockers with `bd dep add`.
**ALWAYS use non-interactive flags** with file operations to avoid hanging on confirmation prompts.
The current Beads database is local-only embedded Dolt. No Git or Dolt remote is configured, so do not claim that changes were pushed. At the end of a session:
Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input.
1. run the relevant quality gates;
2. update or close the active bead;
3. run `bd dep cycles` after dependency changes;
4. export a readable snapshot with `bd export --output .beads/issues.jsonl`;
5. commit local source and tracking changes when appropriate;
6. report explicitly that synchronization remains local until a remote is configured.
**Use these forms instead:**
```bash
# Force overwrite without prompting
cp -f source dest # NOT: cp source dest
mv -f source dest # NOT: mv source dest
rm -f file # NOT: rm file
Do not edit `.beads/issues.jsonl` manually. The source of truth is `.beads/embeddeddolt`.
# For recursive operations
rm -rf directory # NOT: rm -r directory
cp -rf source dest # NOT: cp -r source dest
## Build and tests
```sh
./gradlew clean testDebugUnitTest lintDebug assembleDebug
./gradlew connectedDebugAndroidTest
```
**Other commands that may prompt:**
- `scp` - use `-o BatchMode=yes` for non-interactive
- `ssh` - use `-o BatchMode=yes` to fail instead of prompting
- `apt-get` - use `-y` flag
- `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var
The connected test command requires a running Android device or emulator.
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:7510c1e2 -->
## Beads Issue Tracker
## Product documents
This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
### Quick Reference
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
```
### Rules
- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
- Run `bd prime` for detailed command reference and session close protocol
- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
## Session Completion
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
**MANDATORY WORKFLOW:**
1. **File issues for remaining work** - Create issues for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **PUSH TO REMOTE** - This is MANDATORY:
```bash
git pull --rebase
git push
git status # MUST show "up to date with origin"
```
5. **Clean up** - Clear stashes, prune remote branches
6. **Verify** - All changes committed AND pushed
7. **Hand off** - Provide context for next session
**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds
- NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds
<!-- END BEADS INTEGRATION -->
- `docs/DEVELOPMENT_PLAN.md` — approved GDD + TDD + production roadmap.
- `docs/CURRENT_ROADMAP.md` — current execution sequence.
- `docs/BEADS_WORKFLOW.md` — backlog structure and operator commands.
+3 -68
View File
@@ -1,70 +1,5 @@
# Project Instructions for AI Agents
# Project instructions for Claude Code
This file provides instructions and context for AI coding agents working on this project.
Follow [`AGENTS.md`](AGENTS.md). Work is tracked in local Beads (`bd`), with no remote currently configured.
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:7510c1e2 -->
## Beads Issue Tracker
This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
### Quick Reference
```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
```
### Rules
- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
- Run `bd prime` for detailed command reference and session close protocol
- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
## Session Completion
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
**MANDATORY WORKFLOW:**
1. **File issues for remaining work** - Create issues for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **PUSH TO REMOTE** - This is MANDATORY:
```bash
git pull --rebase
git push
git status # MUST show "up to date with origin"
```
5. **Clean up** - Clear stashes, prune remote branches
6. **Verify** - All changes committed AND pushed
7. **Hand off** - Provide context for next session
**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds
- NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds
<!-- END BEADS INTEGRATION -->
## Build & Test
_Add your build and test commands here_
```bash
# Example:
# npm install
# npm test
```
## Architecture Overview
_Add a brief overview of your project architecture_
## Conventions & Patterns
_Add your project-specific conventions here_
Run `bd prime`, `bd status`, and `bd ready --exclude-type epic` before selecting work. Claim a bead before implementation and update its status only after its acceptance criteria and relevant tests are satisfied.
+36
View File
@@ -0,0 +1,36 @@
# QA report
Date: 2026-08-18
## Automated checks
- Clean Gradle build: `clean testDebugUnitTest lintDebug assembleDebug` — passed.
- Unit tests: 8 passed, 0 failed, 0 skipped.
- Device acceptance tests: 1 passed, 0 failed, 0 skipped.
- Level 5 bot: completed the production level in about 8.2 seconds with exactly one death, one retained corpse, pressure-plate activation, door passage, two jumps, and goal contact.
- Android Lint: no issues found.
- APK archive integrity: all entries passed `unzip -t`.
- Package metadata: `com.projectsacrifice.game.debug`, version code 3, min SDK 23, target SDK 34.
- APK signature: v1 and v2 schemes verified; one standard Android Debug signer.
## Device smoke test
Environment: official ARM64 Android Emulator, Android 16 / API 36, 2400×1080 landscape.
Verified:
1. streamed APK installation returns `Success`;
2. cold launch returns `Status: ok`;
3. `MainActivity` remains visible and `RESUMED`;
4. immersive landscape mode is applied;
5. level-selection screen renders correctly;
6. entering level 1 renders the HUD, character, goal, and mobile controls;
7. holding the right control completes level 1;
8. completion records best time/deaths and unlocks level 2 (`unlocked=2`);
9. reaching spikes in level 2 increments the volunteer counter, keeps the corpse in the level, and respawns a live volunteer without resetting the scene;
10. Android crash buffer remains empty after the corrected build and gameplay smoke tests.
11. the level 5 acceptance bot reproduces the complete saw/body/plate/door solution on the same Android 16 emulator; its initial red runs exposed an unstable saw impulse, and the final run passed after the body delivery was corrected.
## Distribution note
The delivered APK is a debug-signed tester build. It is directly installable, but it is not intended for Play Store publication. A production release should use the publisher's private release keystore and a non-debug application ID.
+63
View File
@@ -0,0 +1,63 @@
# Project Sacrifice — Android vertical slice
An original landscape puzzle-platformer prototype inspired by the broad “death changes the puzzle” genre formula. It does not reuse the title, art, audio, text, characters, or level layouts of any commercial game.
Project documents:
- [`docs/DEVELOPMENT_PLAN.md`](docs/DEVELOPMENT_PLAN.md) — approved GDD + TDD + production roadmap;
- [`docs/CURRENT_ROADMAP.md`](docs/CURRENT_ROADMAP.md) — execution roadmap for the existing vertical slice;
- [`docs/BEADS_WORKFLOW.md`](docs/BEADS_WORKFLOW.md) — local backlog structure and daily Beads commands.
## Playable content
The APK contains ten sequential levels:
1. movement and jumping;
2. bodies covering a spike lane;
3. a corpse holding a pressure plate and door;
4. a toggle-controlled moving lift;
5. a saw launching a corpse onto a remote plate;
6. a frozen corpse becoming a stable step;
7. an electrified corpse completing a relay;
8. a timed switch and moving platform;
9. a two-signal door requiring weight and electricity;
10. a mastery level combining saw, freeze, electricity, pressure plate, pit, moving lift, and final gate.
Death does not reset the level. A new volunteer respawns after a short delay while retained bodies stay in the simulation. The restart button deliberately clears the puzzle state. Progress, best time, and lowest volunteer count are stored locally.
## Controls
- Hold the left/right arrows to move.
- Hold a direction and tap jump simultaneously; multitouch is supported.
- `Restart` restores the current level.
- `II` pauses the game and exposes level selection.
The movement constants include 130 ms coyote time and 140 ms input buffering.
## Build
Requirements: JDK 17 and Android SDK Platform/Build Tools 34.
```sh
./gradlew testDebugUnitTest assembleDebug
```
Level 5 has a device-level acceptance bot. It drives the production `GameView`
through the same movement and jump inputs as the touch controls, sacrifices one
volunteer to the saw, verifies the retained body and pressure-plate door, then
reaches the goal. With an emulator or phone connected, run it with:
```sh
./gradlew connectedDebugAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.projectsacrifice.game.LevelFivePlaythroughTest
```
The debug APK is written to `app/build/outputs/apk/debug/app-debug.apk`. It is signed with Android's standard debug key and can be installed with:
```sh
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
## Scope
This is a dependency-free vertical slice rendered through a custom Android Canvas view. It is intended for gameplay evaluation on phones and tablets, not as a content-complete commercial release.
+45
View File
@@ -0,0 +1,45 @@
plugins {
id 'com.android.application'
}
android {
namespace 'com.projectsacrifice.game'
compileSdk 34
defaultConfig {
applicationId 'com.projectsacrifice.game'
minSdk 23
targetSdk 34
versionCode 3
versionName '0.2.1-level5-proof'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-debug'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
testOptions {
unitTests.returnDefaultValues = true
}
}
dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:rules:1.5.0'
}
+1
View File
@@ -0,0 +1 @@
# No shrinking is enabled for this prototype.
@@ -0,0 +1,70 @@
package com.projectsacrifice.game;
import android.app.Instrumentation;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Acceptance test: a bot must finish the same level implementation shipped in the APK. */
@RunWith(AndroidJUnit4.class)
public final class LevelFivePlaythroughTest {
@Test
public void testBotCompletesLevelFive() {
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
final GameView[] view = new GameView[1];
scenario.onActivity(activity -> view[0] = activity.gameViewForAutomation());
LevelPlaytestBot bot = new LevelPlaytestBot();
LevelPlaytestBot.Result result = bot.proveLevelFive(
new AndroidDriver(InstrumentationRegistry.getInstrumentation(), view[0]),
25_000L
);
org.junit.Assert.assertTrue(result.diagnostic(), result.completed());
org.junit.Assert.assertTrue("The solution must use at least one volunteer", result.deaths() >= 1);
org.junit.Assert.assertTrue("The delivered corpse must remain in the puzzle", result.corpseCount() >= 1);
org.junit.Assert.assertTrue("The pressure-plate door must open", result.doorOpened());
}
}
private static final class AndroidDriver implements LevelPlaytestBot.Driver {
private final Instrumentation instrumentation;
private final GameView gameView;
AndroidDriver(Instrumentation instrumentation, GameView gameView) {
this.instrumentation = instrumentation;
this.gameView = gameView;
}
@Override
public void startLevel(int oneBasedLevelNumber) {
instrumentation.runOnMainSync(() -> gameView.startLevelForAutomation(oneBasedLevelNumber));
}
@Override
public void setInput(boolean moveLeft, boolean moveRight, boolean jump) {
instrumentation.runOnMainSync(() -> gameView.setAutomationInput(moveLeft, moveRight, jump));
}
@Override
public GameView.AutomationSnapshot snapshot() {
final GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
return value[0];
}
@Override
public void waitForNextDecision(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new AssertionError("Bot interrupted", interrupted);
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:glEsVersion="0x00020000" android:required="false" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.ProjectSacrifice">
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,29 @@
package com.projectsacrifice.game;
public final class GameMath {
private GameMath() {}
public static float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}
public static boolean intersects(
float ax, float ay, float aw, float ah,
float bx, float by, float bw, float bh
) {
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
public static float pingPong(float elapsedSeconds, float periodSeconds) {
if (periodSeconds <= 0f) return 0f;
double phase = (elapsedSeconds % periodSeconds) / periodSeconds;
return (float) ((1d - Math.cos(phase * Math.PI * 2d)) * 0.5d);
}
public static String formatTime(long elapsedMs) {
long totalSeconds = Math.max(0L, elapsedMs) / 1000L;
long minutes = totalSeconds / 60L;
long seconds = totalSeconds % 60L;
return String.format(java.util.Locale.US, "%02d:%02d", minutes, seconds);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,194 @@
package com.projectsacrifice.game;
import static com.projectsacrifice.game.LevelDefinition.HazardType.ELECTRIC;
import static com.projectsacrifice.game.LevelDefinition.HazardType.FREEZER;
import static com.projectsacrifice.game.LevelDefinition.HazardType.PIT;
import static com.projectsacrifice.game.LevelDefinition.HazardType.SAW;
import static com.projectsacrifice.game.LevelDefinition.HazardType.SPIKES;
import static com.projectsacrifice.game.LevelDefinition.SwitchType.PRESSURE;
import static com.projectsacrifice.game.LevelDefinition.SwitchType.TIMED;
import static com.projectsacrifice.game.LevelDefinition.SwitchType.TOGGLE;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Ten original vertical-slice puzzles with a one-mechanic-at-a-time learning curve. */
public final class LevelCatalog {
private static final float FLOOR_Y = 880f;
private static final List<LevelDefinition> LEVELS = Collections.unmodifiableList(Arrays.asList(
level1(), level2(), level3(), level4(), level5(),
level6(), level7(), level8(), level9(), level10()
));
private LevelCatalog() {}
public static List<LevelDefinition> all() { return LEVELS; }
public static LevelDefinition get(int index) {
if (index < 0 || index >= LEVELS.size()) {
throw new IllegalArgumentException("Unknown level index: " + index);
}
return LEVELS.get(index);
}
private static LevelDefinition level1() {
return level(
1, "The Volunteer", "Reach the relic. Arrows move; the winged button jumps.",
1920, 130, 760, goal(1690, 770), 0, 30,
platforms(floor(0, 1920), ledge(640, 760, 230), ledge(1060, 690, 250)),
hazards(), switches(), doors()
);
}
private static LevelDefinition level2() {
return level(
2, "A Useful End", "The spikes keep every volunteer. Build a path from what remains.",
2050, 130, 760, goal(1830, 770), 3, 55,
platforms(floor(0, 2050), ledge(1180, 740, 240)),
hazards(hazard(SPIKES, 610, 850, 260, 30)), switches(), doors()
);
}
private static LevelDefinition level3() {
return level(
3, "Dead Weight", "Leave a body on the brass plate so the next volunteer can pass.",
2050, 130, 760, goal(1810, 770), 1, 50,
platforms(floor(0, 2050)),
hazards(hazard(SAW, 565, 760, 92, 92, null, 0, -150)),
switches(sw(PRESSURE, 540, 860, 150, 20, "gate", 0, 1)),
doors(door(1110, 570, 78, 310, "gate"))
);
}
private static LevelDefinition level4() {
return level(
4, "The Long Lift", "Touch the lever, then ride the moving platform to the upper gallery.",
2200, 130, 760, goal(1960, 390), 0, 60,
platforms(
floor(0, 1500), floor(1820, 380), ledge(1660, 500, 540),
moving(1040, 790, 260, 34, 0, -390, 5.2f, "lift")
),
hazards(hazard(PIT, 1500, 880, 320, 200)),
switches(sw(TOGGLE, 520, 820, 72, 60, "lift", 0, 1)), doors()
);
}
private static LevelDefinition level5() {
return level(
5, "Express Delivery", "The saw throws to the right. Deliver a volunteer to the distant plate.",
2300, 130, 760, goal(2080, 770), 1, 65,
platforms(floor(0, 2300), ledge(850, 800, 330)),
hazards(hazard(SAW, 570, 760, 96, 96, null, 500, -700)),
switches(sw(PRESSURE, 930, 780, 160, 20, "gate", 0, 1)),
doors(door(1390, 560, 82, 320, "gate"))
);
}
private static LevelDefinition level6() {
return level(
6, "Cold Comfort", "The frost makes a solid block. Use the frozen volunteer as a step.",
2150, 130, 760, goal(1880, 500), 1, 65,
platforms(floor(0, 1120), ledge(810, 590, 520), ledge(1430, 610, 570)),
hazards(hazard(FREEZER, 690, 750, 100, 130)), switches(), doors()
);
}
private static LevelDefinition level7() {
return level(
7, "Live Wire", "A fallen volunteer conducts the current and keeps the relay alive.",
2150, 130, 760, goal(1930, 770), 1, 55,
platforms(floor(0, 2150)),
hazards(hazard(ELECTRIC, 650, 780, 110, 100, "power", 0, 0)),
switches(), doors(door(1130, 560, 84, 320, "power"))
);
}
private static LevelDefinition level8() {
return level(
8, "Borrowed Time", "The blue switch lasts five seconds. Keep moving.",
2450, 130, 760, goal(2220, 600), 0, 38,
platforms(
floor(0, 900), floor(1510, 940), ledge(1980, 710, 470),
moving(990, 770, 240, 34, 380, 0, 4.0f, "clock")
),
hazards(hazard(PIT, 900, 880, 610, 200)),
switches(sw(TIMED, 560, 810, 90, 70, "clock", 5.5f, 1)),
doors(door(1770, 560, 82, 320, "clock"))
);
}
private static LevelDefinition level9() {
return level(
9, "Two Good Reasons", "The final gate needs weight and power at the same time.",
2550, 130, 760, goal(2310, 770), 2, 80,
platforms(floor(0, 2550)),
hazards(
hazard(SPIKES, 520, 850, 130, 30),
hazard(ELECTRIC, 910, 780, 110, 100, "power", 0, 0)
),
switches(sw(PRESSURE, 510, 842, 160, 38, "weight", 0, 1)),
doors(door(1510, 540, 86, 340, "weight", "power"))
);
}
private static LevelDefinition level10() {
return level(
10, "The Last Procession", "Launch, freeze, conduct, ascend. Every lesson opens the last relic.",
3100, 130, 760, goal(2880, 330), 3, 120,
platforms(
floor(0, 1880), floor(2140, 960), ledge(2420, 440, 680),
moving(1840, 790, 250, 34, 0, -410, 5.0f, "lift")
),
hazards(
hazard(SAW, 520, 760, 96, 96, null, 700, -240),
hazard(FREEZER, 1110, 750, 100, 130),
hazard(ELECTRIC, 1510, 780, 110, 100, "power", 0, 0),
hazard(PIT, 1880, 880, 260, 200)
),
switches(sw(PRESSURE, 860, 860, 160, 20, "lift", 0, 1)),
doors(door(2310, 310, 82, 570, "power"))
);
}
private static LevelDefinition level(
int number, String title, String hint, float width,
float spawnX, float spawnY, LevelDefinition.Box goal,
int targetDeaths, int targetSeconds,
List<LevelDefinition.Platform> platforms,
List<LevelDefinition.Hazard> hazards,
List<LevelDefinition.Switch> switches,
List<LevelDefinition.Door> doors
) {
return new LevelDefinition(number, title, hint, width, spawnX, spawnY, goal,
targetDeaths, targetSeconds, platforms, hazards, switches, doors);
}
private static LevelDefinition.Box box(float x, float y, float w, float h) {
return new LevelDefinition.Box(x, y, w, h);
}
private static LevelDefinition.Box goal(float x, float y) { return box(x, y, 86, 110); }
private static LevelDefinition.Platform floor(float x, float w) { return new LevelDefinition.Platform(box(x, FLOOR_Y, w, 200)); }
private static LevelDefinition.Platform ledge(float x, float y, float w) { return new LevelDefinition.Platform(box(x, y, w, 34)); }
private static LevelDefinition.Platform moving(float x, float y, float w, float h, float tx, float ty, float period, String control) {
return new LevelDefinition.Platform(box(x, y, w, h), tx, ty, period, control);
}
private static LevelDefinition.Hazard hazard(LevelDefinition.HazardType type, float x, float y, float w, float h) {
return new LevelDefinition.Hazard(type, box(x, y, w, h));
}
private static LevelDefinition.Hazard hazard(LevelDefinition.HazardType type, float x, float y, float w, float h, String relay, float ix, float iy) {
return new LevelDefinition.Hazard(type, box(x, y, w, h), relay, ix, iy);
}
private static LevelDefinition.Switch sw(LevelDefinition.SwitchType type, float x, float y, float w, float h, String id, float duration, int mass) {
return new LevelDefinition.Switch(type, box(x, y, w, h), id, duration, mass);
}
private static LevelDefinition.Door door(float x, float y, float w, float h, String... ids) {
return new LevelDefinition.Door(box(x, y, w, h), ids);
}
@SafeVarargs private static List<LevelDefinition.Platform> platforms(LevelDefinition.Platform... values) { return Arrays.asList(values); }
@SafeVarargs private static List<LevelDefinition.Hazard> hazards(LevelDefinition.Hazard... values) { return Arrays.asList(values); }
@SafeVarargs private static List<LevelDefinition.Switch> switches(LevelDefinition.Switch... values) { return Arrays.asList(values); }
@SafeVarargs private static List<LevelDefinition.Door> doors(LevelDefinition.Door... values) { return Arrays.asList(values); }
}
@@ -0,0 +1,161 @@
package com.projectsacrifice.game;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Immutable, Android-independent level data. */
public final class LevelDefinition {
public enum HazardType { SPIKES, SAW, FREEZER, ELECTRIC, PIT }
public enum SwitchType { PRESSURE, TOGGLE, TIMED }
public static final class Box {
public final float x;
public final float y;
public final float width;
public final float height;
public Box(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public float right() { return x + width; }
public float bottom() { return y + height; }
}
public static final class Platform {
public final Box box;
public final float travelX;
public final float travelY;
public final float periodSeconds;
public final String controlId;
public Platform(Box box) {
this(box, 0f, 0f, 0f, null);
}
public Platform(Box box, float travelX, float travelY, float periodSeconds, String controlId) {
this.box = box;
this.travelX = travelX;
this.travelY = travelY;
this.periodSeconds = periodSeconds;
this.controlId = controlId;
}
public boolean isMoving() { return travelX != 0f || travelY != 0f; }
}
public static final class Hazard {
public final HazardType type;
public final Box box;
public final String relayId;
public final float impulseX;
public final float impulseY;
public Hazard(HazardType type, Box box) {
this(type, box, null, 0f, 0f);
}
public Hazard(HazardType type, Box box, String relayId, float impulseX, float impulseY) {
this.type = type;
this.box = box;
this.relayId = relayId;
this.impulseX = impulseX;
this.impulseY = impulseY;
}
}
public static final class Switch {
public final SwitchType type;
public final Box box;
public final String signalId;
public final float durationSeconds;
public final int requiredMass;
public Switch(SwitchType type, Box box, String signalId, float durationSeconds, int requiredMass) {
this.type = type;
this.box = box;
this.signalId = signalId;
this.durationSeconds = durationSeconds;
this.requiredMass = requiredMass;
}
}
public static final class Door {
public final Box box;
public final List<String> requiredSignals;
public Door(Box box, String... requiredSignals) {
this.box = box;
this.requiredSignals = Collections.unmodifiableList(Arrays.asList(requiredSignals));
}
}
public final int number;
public final String title;
public final String hint;
public final float worldWidth;
public final float spawnX;
public final float spawnY;
public final Box goal;
public final int targetDeaths;
public final int targetSeconds;
public final List<Platform> platforms;
public final List<Hazard> hazards;
public final List<Switch> switches;
public final List<Door> doors;
public LevelDefinition(
int number,
String title,
String hint,
float worldWidth,
float spawnX,
float spawnY,
Box goal,
int targetDeaths,
int targetSeconds,
List<Platform> platforms,
List<Hazard> hazards,
List<Switch> switches,
List<Door> doors
) {
this.number = number;
this.title = title;
this.hint = hint;
this.worldWidth = worldWidth;
this.spawnX = spawnX;
this.spawnY = spawnY;
this.goal = goal;
this.targetDeaths = targetDeaths;
this.targetSeconds = targetSeconds;
this.platforms = Collections.unmodifiableList(platforms);
this.hazards = Collections.unmodifiableList(hazards);
this.switches = Collections.unmodifiableList(switches);
this.doors = Collections.unmodifiableList(doors);
}
public boolean containsMechanic(HazardType type) {
for (Hazard hazard : hazards) {
if (hazard.type == type) return true;
}
return false;
}
public boolean containsMechanic(SwitchType type) {
for (Switch item : switches) {
if (item.type == type) return true;
}
return false;
}
public boolean hasMovingPlatform() {
for (Platform platform : platforms) {
if (platform.isMoving()) return true;
}
return false;
}
}
@@ -0,0 +1,91 @@
package com.projectsacrifice.game;
/**
* Black-box playtest bot for the fifth level. It operates through the same input
* flags used by touch controls and observes only player-visible session state.
*/
public final class LevelPlaytestBot {
public interface Driver {
void startLevel(int oneBasedLevelNumber);
void setInput(boolean moveLeft, boolean moveRight, boolean jump);
GameView.AutomationSnapshot snapshot();
void waitForNextDecision(long millis);
}
public static final class Result {
private final boolean completed;
private final boolean doorOpened;
private final int deaths;
private final int corpseCount;
private final String diagnostic;
Result(boolean completed, boolean doorOpened, int deaths, int corpseCount, String diagnostic) {
this.completed = completed;
this.doorOpened = doorOpened;
this.deaths = deaths;
this.corpseCount = corpseCount;
this.diagnostic = diagnostic;
}
public boolean completed() { return completed; }
public boolean doorOpened() { return doorOpened; }
public int deaths() { return deaths; }
public int corpseCount() { return corpseCount; }
public String diagnostic() { return diagnostic; }
}
public Result proveLevelFive(Driver driver, long timeoutMillis) {
driver.startLevel(5);
long deadline = System.currentTimeMillis() + timeoutMillis;
boolean doorOpened = false;
int jumpStage = 0;
int respawnsAfterSacrifice = 0;
boolean wasAlive = true;
GameView.AutomationSnapshot last = driver.snapshot();
while (System.currentTimeMillis() < deadline) {
last = driver.snapshot();
doorOpened |= last.allDoorsOpen();
if (last.completed()) {
driver.setInput(false, false, false);
return new Result(true, doorOpened, last.deaths(), last.corpseCount(),
"Level 5 completed by bot: deaths=" + last.deaths()
+ ", corpses=" + last.corpseCount() + ", doorOpened=" + doorOpened);
}
if (!last.playerAlive()) {
driver.setInput(false, false, false);
wasAlive = false;
} else if (last.deaths() == 0) {
driver.setInput(false, true, false);
} else if (!doorOpened) {
driver.setInput(false, false, false);
} else {
if (!wasAlive) {
respawnsAfterSacrifice++;
jumpStage = 0;
}
if (jumpStage == 0 && last.playerX() < 390f) {
driver.setInput(false, true, false);
} else if (jumpStage == 0) {
driver.setInput(false, true, true);
jumpStage = 1;
} else if (jumpStage == 1 && last.playerX() >= 750f) {
driver.setInput(false, true, true);
jumpStage = 2;
} else {
driver.setInput(false, true, false);
}
wasAlive = true;
}
driver.waitForNextDecision(32L);
}
driver.setInput(false, false, false);
return new Result(false, doorOpened, last.deaths(), last.corpseCount(),
"Bot timed out on level 5: x=" + last.playerX() + ", y=" + last.playerY()
+ ", alive=" + last.playerAlive() + ", deaths=" + last.deaths()
+ ", corpses=" + last.corpseCount() + ", doorOpened=" + doorOpened
+ ", respawnsAfterSacrifice=" + respawnsAfterSacrifice);
}
}
@@ -0,0 +1,70 @@
package com.projectsacrifice.game;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
public final class MainActivity extends Activity {
private GameView gameView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
gameView = new GameView(this);
setContentView(gameView);
hideSystemUi();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) hideSystemUi();
}
@Override
protected void onPause() {
super.onPause();
if (gameView != null) gameView.setPaused(true);
}
@Override
protected void onResume() {
super.onResume();
hideSystemUi();
}
@Override
public void onBackPressed() {
if (gameView != null && gameView.handleBack()) return;
super.onBackPressed();
}
private void hideSystemUi() {
if (android.os.Build.VERSION.SDK_INT >= 30) {
WindowInsetsController controller = getWindow().getInsetsController();
if (controller != null) {
controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
} else {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
);
}
}
public GameView gameViewForAutomation() {
return gameView;
}
}
@@ -0,0 +1,42 @@
package com.projectsacrifice.game;
import android.content.Context;
import android.content.SharedPreferences;
public final class ProgressStore {
private static final String FILE = "project_sacrifice_progress_v1";
private final SharedPreferences preferences;
public ProgressStore(Context context) {
preferences = context.getSharedPreferences(FILE, Context.MODE_PRIVATE);
}
public int unlockedCount() {
return Math.max(1, Math.min(LevelCatalog.all().size(), preferences.getInt("unlocked", 1)));
}
public boolean isCompleted(int levelNumber) {
return preferences.getBoolean(key(levelNumber, "completed"), false);
}
public int bestDeaths(int levelNumber) {
return preferences.getInt(key(levelNumber, "deaths"), Integer.MAX_VALUE);
}
public long bestTimeMs(int levelNumber) {
return preferences.getLong(key(levelNumber, "time"), Long.MAX_VALUE);
}
public void recordCompletion(int levelNumber, int deaths, long elapsedMs) {
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(key(levelNumber, "completed"), true);
editor.putInt(key(levelNumber, "deaths"), Math.min(deaths, bestDeaths(levelNumber)));
editor.putLong(key(levelNumber, "time"), Math.min(elapsedMs, bestTimeMs(levelNumber)));
editor.putInt("unlocked", Math.min(LevelCatalog.all().size(), Math.max(unlockedCount(), levelNumber + 1)));
editor.apply();
}
private static String key(int levelNumber, String suffix) {
return "level_" + levelNumber + "_" + suffix;
}
}
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:fillColor="#111827" android:pathData="M0,0h108v108h-108z" />
<path android:fillColor="#F6C453" android:pathData="M54,14l31,18v36l-31,26l-31,-26v-36z" />
<path android:fillColor="#374151" android:pathData="M54,25l20,11v27l-20,17l-20,-17v-27z" />
<path android:fillColor="#F4E6C7" android:pathData="M48,33h12v35h-12zM38,45h32v10h-32z" />
</vector>
+5
View File
@@ -0,0 +1,5 @@
<resources>
<color name="deep_night">#111827</color>
<color name="parchment">#F4E6C7</color>
<color name="sacrifice_gold">#F6C453</color>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Project Sacrifice</string>
</resources>
+10
View File
@@ -0,0 +1,10 @@
<resources>
<style name="Theme.ProjectSacrifice" parent="android:style/Theme.Material.Light.NoActionBar">
<item name="android:fontFamily">sans</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:colorAccent">@color/sacrifice_gold</item>
<item name="android:navigationBarColor">@color/deep_night</item>
<item name="android:windowBackground">@color/parchment</item>
</style>
</resources>
@@ -0,0 +1,32 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public final class GameMathTest {
@Test
public void intersectionTreatsTouchingEdgesAsNonOverlap() {
assertFalse(GameMath.intersects(0, 0, 10, 10, 10, 0, 10, 10));
assertTrue(GameMath.intersects(0, 0, 10, 10, 9.9f, 0, 10, 10));
}
@Test
public void pingPongIsDeterministicAndBounded() {
assertEquals(0f, GameMath.pingPong(0f, 4f), 0.0001f);
assertEquals(1f, GameMath.pingPong(2f, 4f), 0.0001f);
assertEquals(0f, GameMath.pingPong(4f, 4f), 0.0001f);
for (int i = 0; i < 100; i++) {
float value = GameMath.pingPong(i * 0.13f, 3.7f);
assertTrue(value >= 0f && value <= 1f);
}
}
@Test
public void timeFormattingIsStable() {
assertEquals("00:00", GameMath.formatTime(999));
assertEquals("01:05", GameMath.formatTime(65_000));
}
}
@@ -0,0 +1,105 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public final class LevelCatalogTest {
@Test
public void verticalSliceContainsTenSequentialLevels() {
List<LevelDefinition> levels = LevelCatalog.all();
assertEquals(10, levels.size());
Set<String> titles = new HashSet<>();
for (int i = 0; i < levels.size(); i++) {
LevelDefinition level = levels.get(i);
assertEquals(i + 1, level.number);
assertTrue(titles.add(level.title));
assertFalse(level.hint.trim().isEmpty());
}
}
@Test
public void everyLevelHasValidSpawnGoalAndGeometry() {
for (LevelDefinition level : LevelCatalog.all()) {
assertTrue(level.worldWidth >= 1920f);
assertTrue(level.spawnX >= 0f && level.spawnX < level.worldWidth);
assertTrue(level.spawnY >= 0f && level.spawnY < 1080f);
assertTrue(level.goal.x >= 0f && level.goal.right() <= level.worldWidth);
assertTrue(level.goal.y >= 0f && level.goal.bottom() <= 1080f);
assertFalse(level.platforms.isEmpty());
assertTrue(level.targetDeaths >= 0);
assertTrue(level.targetSeconds > 0);
}
}
@Test
public void requestedMechanicsAreRepresentedInProgression() {
boolean spikes = false;
boolean saw = false;
boolean freezer = false;
boolean electricity = false;
boolean moving = false;
boolean pressure = false;
boolean toggle = false;
boolean timed = false;
boolean multiSignalDoor = false;
for (LevelDefinition level : LevelCatalog.all()) {
spikes |= level.containsMechanic(LevelDefinition.HazardType.SPIKES);
saw |= level.containsMechanic(LevelDefinition.HazardType.SAW);
freezer |= level.containsMechanic(LevelDefinition.HazardType.FREEZER);
electricity |= level.containsMechanic(LevelDefinition.HazardType.ELECTRIC);
moving |= level.hasMovingPlatform();
pressure |= level.containsMechanic(LevelDefinition.SwitchType.PRESSURE);
toggle |= level.containsMechanic(LevelDefinition.SwitchType.TOGGLE);
timed |= level.containsMechanic(LevelDefinition.SwitchType.TIMED);
for (LevelDefinition.Door door : level.doors) multiSignalDoor |= door.requiredSignals.size() > 1;
}
assertTrue(spikes);
assertTrue(saw);
assertTrue(freezer);
assertTrue(electricity);
assertTrue(moving);
assertTrue(pressure);
assertTrue(toggle);
assertTrue(timed);
assertTrue(multiSignalDoor);
}
@Test
public void masteryLevelCombinesAtLeastFourCoreMechanics() {
LevelDefinition finale = LevelCatalog.get(9);
int count = finale.hazards.size() + finale.switches.size();
assertTrue(finale.hasMovingPlatform());
assertTrue(count >= 5);
assertFalse(finale.doors.isEmpty());
}
@Test
public void everyDoorAndControlledPlatformHasADeclaredSignalSource() {
for (LevelDefinition level : LevelCatalog.all()) {
Set<String> sources = new HashSet<>();
for (LevelDefinition.Switch item : level.switches) sources.add(item.signalId);
for (LevelDefinition.Hazard hazard : level.hazards) {
if (hazard.relayId != null) sources.add(hazard.relayId);
}
for (LevelDefinition.Door door : level.doors) {
for (String required : door.requiredSignals) {
assertTrue("Level " + level.number + " has missing signal " + required, sources.contains(required));
}
}
for (LevelDefinition.Platform platform : level.platforms) {
if (platform.controlId != null) {
assertTrue("Level " + level.number + " has missing signal " + platform.controlId, sources.contains(platform.controlId));
}
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'com.android.application' version '8.2.2' apply false
}
+65
View File
@@ -0,0 +1,65 @@
# Beads: отслеживание выполнения плана
Beads инициализирован локально в `~/tmp/goon-game` поверх embedded Dolt. Remote не настроен: база задач существует только на этой машине.
Источники требований:
- [`DEVELOPMENT_PLAN.md`](DEVELOPMENT_PLAN.md) — утверждённый GDD + TDD + production roadmap;
- [`CURRENT_ROADMAP.md`](CURRENT_ROADMAP.md) — ближайшая последовательность работ для существующего vertical slice.
## Структура backlog
- master epic: `goon-game-plan`;
- 16 доменных эпиков `goon-game-e01` … `goon-game-e16` из раздела 62 основного плана;
- milestones Prototype, Vertical Slice, Production, Alpha, Beta и Release Candidate;
- предметные задачи с acceptance criteria, приоритетами и зависимостями;
- уже подтверждённые кодом и QA элементы закрыты с причиной.
На момент инициализации создано 68 beads: 13 закрыты, 4 находятся в работе и 51 открыт. Эти числа нельзя трактовать как точный процент готовности, потому что в базе смешаны эпики, features и задачи разного размера.
Текущий gate — `goon-game-milestone-vslice`. Первая приоритетная незаблокированная задача — `goon-game-bot-framework`.
## Ежедневные команды
Выполнять из `~/tmp/goon-game`:
```sh
bd status
bd ready --exclude-type epic
bd list --all --tree
```
Посмотреть задачу и её зависимости:
```sh
bd show goon-game-bot-framework
bd dep tree goon-game-milestone-vslice
```
Начать работу:
```sh
bd update goon-game-bot-framework --claim
```
Закрыть после выполнения acceptance criteria:
```sh
bd close goon-game-bot-framework --reason "Acceptance criteria выполнены; проверки зелёные"
```
После изменения графа:
```sh
bd dep cycles
bd export --output .beads/issues.jsonl
```
## Правила ведения
1. Не закрывать задачу по факту написания кода — сначала выполнить её acceptance criteria и проверки.
2. Новую найденную работу добавлять дочерней к одному из 16 эпиков и связывать blocking dependency, если она влияет на milestone.
3. Реальные softlocks и непроходимые уровни заводить как `bug` с P0/P1, а не прятать внутри общего balance task.
4. Vertical Slice закрывать только после `10/10 passed`, внешнего playtest и остальных зависимостей в его дереве.
5. Не редактировать `.beads/issues.jsonl` вручную: source of truth — embedded Dolt в `.beads/embeddeddolt`.
6. Перед передачей базы другой машине отдельно настроить Dolt remote либо передать весь Git/Beads-каталог; сейчас удалённой синхронизации нет.
+181
View File
@@ -0,0 +1,181 @@
# Текущий roadmap реализации Project Sacrifice
Этот документ описывает ближайшие работы относительно существующего vertical slice. Полный утверждённый GDD + TDD + production roadmap находится в [`DEVELOPMENT_PLAN.md`](DEVELOPMENT_PLAN.md).
Актуально на 18 августа 2026 года.
## 1. Текущее состояние
Проект находится на стадии проверенного vertical slice:
- реализованы 10 последовательных уровней;
- работают движение, прыжок и мобильное multitouch-управление;
- смерть не сбрасывает состояние уровня, тела остаются физическими объектами;
- реализованы шипы, пила, нажимные и временные кнопки, двери, движущиеся платформы, заморозка и электричество;
- сохраняются прогресс, лучшее время и минимальное число жертв;
- уровень 5 защищён Android acceptance-тестом: бот проходит реальную игровую сцену примерно за 8,2 секунды с одной смертью и одним оставшимся телом;
- финальный debug APK проверен на Android 16: установка, холодный запуск, ZIP-структура и v1/v2-подписи корректны.
Главный текущий риск: автоматическая проходимость доказана только для уровня 5. Остальные уровни могут содержать тупики, нестабильную физику или слишком точные действия.
## 2. Принципы разработки
1. Сначала acceptance-тест, затем исправление уровня.
2. Бот использует те же команды движения и прыжка, что и игрок, без прямого перемещения персонажа или подмены физики.
3. Новый контент не добавляется, пока текущие 10 уровней не проходят обязательный регрессионный набор.
4. Изменения физики проверяются на всех уровнях, потому что скорость, импульсы и коллизии являются общими.
5. Debug APK используется для внутренней проверки; внешняя альфа собирается отдельной release-подписью.
Оценки ниже указаны в рабочих днях для одного Android-разработчика при доступности дизайнера и QA на проверках. Это ориентиры, а не календарные обещания.
## 3. Этапы
### Этап 1. Боты проходимости для уровней 1–10 — P0, 4–6 дней
Задачи:
- выделить общий сценарный движок поверх существующего `LevelPlaytestBot.Driver`;
- добавить наблюдаемые состояния: положение и скорость игрока, опора, активные сигналы, состояние платформ и дверей;
- реализовать отдельный маршрут для каждого уровня;
- выдавать полезную диагностику таймаута: координаты, число смертей, тела, сигналы и последняя выполненная команда;
- запускать все сценарии одной Gradle-командой на эмуляторе;
- исключить случайные повторы как способ скрыть нестабильность.
Критерии завершения:
- уровни 1–10 проходят по три последовательных запуска;
- каждый сценарий использует предусмотренную механику уровня;
- один полный прогон укладывается в 5 минут;
- падение теста показывает этап и состояние, на котором бот остановился.
### Этап 2. Исправление уровней и баланс — P0, 4–7 дней
Задачи:
- исправить найденные ботами тупики и нестабильные взаимодействия;
- увеличить допуски у прыжков и зон срабатывания, не убирая смысл головоломок;
- проверить, что тела не выталкиваются с плит и не застревают в дверях;
- выровнять кривую сложности: знакомство, закрепление, комбинация механик;
- уточнить подсказки и целевые показатели времени/жертв;
- провести минимум две ручные сессии на каждом уровне.
Критерии завершения:
- `10/10 passed` в автоматическом прогоне;
- каждый уровень впервые понимается тестировщиком не более чем за три попытки либо содержит достаточную подсказку;
- restart всегда возвращает чистое начальное состояние;
- после смерти невозможно потерять обязательный объект без возможности перезапуска.
### Этап 3. Управление, интерфейс и доступность — P1, 3–5 дней
Задачи:
- добавить настройку размера и прозрачности экранных кнопок;
- улучшить зоны multitouch и визуальную обратную связь на нажатия;
- добавить короткое обучение без блокирующих длинных окон;
- доработать экран завершения: время, жертвы, лучший результат и следующая цель;
- локализовать интерфейс и подсказки на русский и английский;
- проверить контраст, масштаб текста, вибрацию и режим без вибрации;
- корректно обрабатывать pause/resume и системную кнопку Back.
Критерии завершения:
- игра полностью управляется на телефоне без внешней клавиатуры;
- элементы управления не перекрывают игровую информацию на поддерживаемых экранах;
- смена языка и настроек не сбрасывает прогресс;
- UI проверен минимум на форматах 16:9, 20:9 и планшете.
### Этап 4. Контентная архитектура и инструменты — P1, 5–8 дней
Задачи:
- перенести описания уровней из Java-констант в валидируемый JSON-формат;
- добавить загрузчик с проверкой геометрии, сигналов и обязательных объектов;
- создать простой desktop-скрипт предпросмотра или экспортёр координат;
- закрепить версию физики и формата уровня;
- сохранять воспроизводимый журнал команд бота для расследования регрессий.
Критерии завершения:
- геометрию и подсказки уровня можно изменить без перекомпиляции игрового кода;
- некорректный сигнал, дверь или объект отклоняется валидатором при сборке;
- все существующие уровни после миграции проходят тех же ботов.
### Этап 5. Художественная и звуковая альфа — P1, 8–15 дней
Задачи:
- утвердить самостоятельный визуальный стиль, не копирующий коммерческий референс;
- заменить prototype-графику персонажа, опасностей, окружения и интерфейса;
- добавить анимации движения, смерти, respawn и реакции механизмов;
- добавить звуки управления, механизмов, результата и фоновую музыку;
- предусмотреть независимые настройки музыки, эффектов и вибрации;
- оптимизировать ресурсы для Android.
Критерии завершения:
- все игровые состояния визуально различимы без чтения внутренней логики;
- звук не является единственным источником обязательной информации;
- проект сохраняет стабильную частоту кадров на целевых устройствах.
### Этап 6. Техническая стабилизация — P0 перед внешней альфой, 4–7 дней
Задачи:
- проверить Android 8–16 и устройства с 2–4 ГБ памяти;
- измерить frame time, память, запуск и размер APK;
- протестировать сворачивание, возврат, блокировку экрана и уничтожение Activity;
- добавить журналирование необработанных ошибок без персональных данных;
- проверить обновление приложения поверх предыдущей версии и сохранность прогресса;
- подготовить release build, отдельный application ID для тестового канала и закрыто хранимый keystore;
- сформировать APK для прямой установки и AAB для Google Play Internal Testing.
Критерии завершения:
- нет блокирующих падений, ANR и потери прогресса;
- 95-й перцентиль кадра соответствует целевым 60 FPS либо документированному минимуму 30 FPS на слабых устройствах;
- release APK/AAB подписан, устанавливается и запускается на чистом устройстве;
- debug-инструменты не попадают в публичную конфигурацию без необходимости.
### Этап 7. Закрытая альфа — 1–2 итерации по 5–7 дней
Задачи:
- привлечь 20–30 тестировщиков разного игрового опыта;
- собирать по каждому уровню время, число смертей, restart и точку выхода;
- разделять дефекты на блокирующие, значимые и косметические;
- повторно проверять исправления ботами и ручным smoke-тестом;
- определить уровни с высоким процентом выхода или неверно понятой механикой.
Критерии завершения:
- 90% тестировщиков завершают уровни 1–5 без помощи разработчика;
- 70% завершают все 10 уровней;
- отсутствуют открытые блокирующие дефекты;
- все значимые дефекты имеют решение или явно принятое продуктовое решение.
## 4. Обязательный набор проверок каждого APK
Перед передачей тестировщикам должны выполняться:
1. `testDebugUnitTest` — математика, каталог и валидация уровней;
2. `lintDebug` — Android Lint без ошибок;
3. `connectedDebugAndroidTest` — боты уровней 1–10;
4. проверка APK через `unzip -t`;
5. проверка package/version через `aapt2 dump badging`;
6. проверка подписи через `apksigner verify`;
7. установка поверх предыдущего билда и на чистое устройство;
8. холодный запуск и проверка crash-буфера;
9. ручной smoke-тест уровня 1 и одного сложного комбинированного уровня.
## 5. Ближайшая итерация
Следующая итерация ограничивается этапом 1:
- обобщить существующего бота уровня 5;
- добавить ботов уровней 1–4;
- затем добавить ботов уровней 6–10;
- зафиксировать единый отчёт `10/10 passed`;
- не добавлять уровень 11 и новые механики до выполнения этого критерия.
Результатом итерации должны стать обновлённый исходный проект, проверенный APK и отчёт с длительностью и результатом каждого сценария.
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.nonTransitiveRClass=true
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+92
View File
@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+18
View File
@@ -0,0 +1,18 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "ProjectSacrifice"
include ':app'