Initialize Project Sacrifice backlog and source baseline
This commit is contained in:
@@ -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"}}
|
||||
|
||||
@@ -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
@@ -5,7 +5,7 @@ local.properties
|
||||
*.iml
|
||||
captures/
|
||||
|
||||
# Beads / Dolt files (added by bd init)
|
||||
# Beads / Dolt local runtime
|
||||
.dolt/
|
||||
*.db
|
||||
.beads-credential-key
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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'
|
||||
}
|
||||
Vendored
+1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<color name="deep_night">#111827</color>
|
||||
<color name="parchment">#F4E6C7</color>
|
||||
<color name="sacrifice_gold">#F6C453</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Project Sacrifice</string>
|
||||
</resources>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '8.2.2' apply false
|
||||
}
|
||||
@@ -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-каталог; сейчас удалённой синхронизации нет.
|
||||
@@ -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
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
android.nonTransitiveRClass=true
|
||||
Vendored
BIN
Binary file not shown.
+7
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user