Завершить инженерную проверку этапа 2
This commit is contained in:
@@ -21,3 +21,5 @@
|
|||||||
{"id":"int-ab10aee3","kind":"field_change","created_at":"2026-08-18T01:30:37.522248Z","actor":"E.Gavrilov","issue_id":"goon-game-level6-freezer-overlap","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"FREEZER больше не убивает игрока на frozen stepping stone; floor lethality сохранена; уровень 6 проходит 3/3."}}
|
{"id":"int-ab10aee3","kind":"field_change","created_at":"2026-08-18T01:30:37.522248Z","actor":"E.Gavrilov","issue_id":"goon-game-level6-freezer-overlap","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"FREEZER больше не убивает игрока на frozen stepping stone; floor lethality сохранена; уровень 6 проходит 3/3."}}
|
||||||
{"id":"int-129af3b6","kind":"field_change","created_at":"2026-08-18T01:30:42.004043Z","actor":"E.Gavrilov","issue_id":"goon-game-level8-softlock","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Expiry справа от PIT воспроизводился; recovery relay и баланс таймера проверены intended 3/3 и recovery 3/3."}}
|
{"id":"int-129af3b6","kind":"field_change","created_at":"2026-08-18T01:30:42.004043Z","actor":"E.Gavrilov","issue_id":"goon-game-level8-softlock","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Expiry справа от PIT воспроизводился; recovery relay и баланс таймера проверены intended 3/3 и recovery 3/3."}}
|
||||||
{"id":"int-2dabc667","kind":"field_change","created_at":"2026-08-18T01:30:46.06691Z","actor":"E.Gavrilov","issue_id":"goon-game-bots-6-10","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Уровни 6–10 проходят intended routes по 3 раза; уровни 8 и 10 дополнительно проходят 96ms stress checks."}}
|
{"id":"int-2dabc667","kind":"field_change","created_at":"2026-08-18T01:30:46.06691Z","actor":"E.Gavrilov","issue_id":"goon-game-bots-6-10","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Уровни 6–10 проходят intended routes по 3 раза; уровни 8 и 10 дополнительно проходят 96ms stress checks."}}
|
||||||
|
{"id":"int-2cbd2a1d","kind":"field_change","created_at":"2026-08-18T10:41:46.8206Z","actor":"E.Gavrilov","issue_id":"goon-game-level10-sliding-corpse","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Acceptance met: delivered body remains on lift plate, signal stays active, normal 3x and delayed-input level 10 routes pass."}}
|
||||||
|
{"id":"int-a046aa8d","kind":"field_change","created_at":"2026-08-18T10:41:48.065633Z","actor":"E.Gavrilov","issue_id":"goon-game-level-route-risks","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"All intended risky routes are reproduced by connected bots; possible L2 alternative is explicitly accepted and documented as non-blocking."}}
|
||||||
|
|||||||
+4
-3
@@ -1,9 +1,10 @@
|
|||||||
|
{"_type":"issue","id":"goon-game-level10-sliding-corpse","title":"Остановить скольжение тела на плите уровня 10","description":"Брошенное пилой тело уровня 10 может продолжать скользить по полу и съезжать с PRESSURE lift, из-за чего движущаяся платформа теряет сигнал и intended route становится нестабильным.","acceptance_criteria":"После доставки тело стабильно остаётся на PRESSURE lift без ручного тайминга; сигнал lift не пропадает при длительном ожидании; уровень 10 проходит normal и delayed-input маршруты по 3 раза без дополнительных смертей.","notes":"2026-08-18: focused RED showed firstCorpseX drift 791.4791 -\u003e 866.5033 in 750 ms. Grounded bodies now settle fully inside PRESSURE, stop, and pin. GREEN: position stable, lift powered after 6 s, exactly 1 death/body; level 10 delayed run and 3 consecutive mastery runs passed; final unfiltered matrix 16/16.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"closed","priority":0,"issue_type":"bug","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-18T08:39:58Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T10:41:47Z","started_at":"2026-08-18T08:40:02Z","closed_at":"2026-08-18T10:41:47Z","close_reason":"Acceptance met: delivered body remains on lift plate, signal stays active, normal 3x and delayed-input level 10 routes pass.","labels":["bug","current","level10","physics"],"dependencies":[{"issue_id":"goon-game-level10-sliding-corpse","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T11:40:01Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
|
||||||
{"_type":"issue","id":"goon-game-level6-freezer-overlap","title":"Исправить повторную смерть на frozen corpse уровня 6","description":"Frozen corpse уровня 6 фиксируется на y=776..880, а FREEZER остаётся y=750..880; игрок на теле занимает y=676..776 и пересекает hazard на 26 px, поэтому intended stepping-stone route вызывает немедленную вторую смерть.","acceptance_criteria":"Игрок может стоять и прыгнуть с предусмотренного frozen corpse без повторной FREEZE-смерти; вход в FREEZER с пола всё ещё убивает; intended route уровня 6 проходит три раза подряд production-input ботом.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"closed","priority":0,"issue_type":"bug","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T22:46:43Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:37Z","started_at":"2026-08-17T22:46:52Z","closed_at":"2026-08-18T01:30:37Z","close_reason":"FREEZER больше не убивает игрока на frozen stepping stone; floor lethality сохранена; уровень 6 проходит 3/3.","labels":["bug","current","freeze","level6"],"dependencies":[{"issue_id":"goon-game-level6-freezer-overlap","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T01:46:51Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
|
{"_type":"issue","id":"goon-game-level6-freezer-overlap","title":"Исправить повторную смерть на frozen corpse уровня 6","description":"Frozen corpse уровня 6 фиксируется на y=776..880, а FREEZER остаётся y=750..880; игрок на теле занимает y=676..776 и пересекает hazard на 26 px, поэтому intended stepping-stone route вызывает немедленную вторую смерть.","acceptance_criteria":"Игрок может стоять и прыгнуть с предусмотренного frozen corpse без повторной FREEZE-смерти; вход в FREEZER с пола всё ещё убивает; intended route уровня 6 проходит три раза подряд production-input ботом.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"closed","priority":0,"issue_type":"bug","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T22:46:43Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:37Z","started_at":"2026-08-17T22:46:52Z","closed_at":"2026-08-18T01:30:37Z","close_reason":"FREEZER больше не убивает игрока на frozen stepping stone; floor lethality сохранена; уровень 6 проходит 3/3.","labels":["bug","current","freeze","level6"],"dependencies":[{"issue_id":"goon-game-level6-freezer-overlap","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T01:46:51Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
|
||||||
{"_type":"issue","id":"goon-game-level8-softlock","title":"Устранить softlock таймера уровня 8","description":"Если clock истекает после пересечения PIT, дверь закрывается, платформа останавливается, а переключатель остаётся слева: игрок может оказаться заперт справа до restart.","acceptance_criteria":"Бот воспроизводит expiry после PIT; игрок всегда имеет путь к restart-free recovery или таймер/геометрия исключают ловушку; сценарий проходит 3 раза подряд.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"closed","priority":0,"issue_type":"bug","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T22:09:40Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:42Z","started_at":"2026-08-17T22:33:08Z","closed_at":"2026-08-18T01:30:42Z","close_reason":"Expiry справа от PIT воспроизводился; recovery relay и баланс таймера проверены intended 3/3 и recovery 3/3.","labels":["bug","current","level8","softlock"],"dependencies":[{"issue_id":"goon-game-level8-softlock","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T01:09:48Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
|
{"_type":"issue","id":"goon-game-level8-softlock","title":"Устранить softlock таймера уровня 8","description":"Если clock истекает после пересечения PIT, дверь закрывается, платформа останавливается, а переключатель остаётся слева: игрок может оказаться заперт справа до restart.","acceptance_criteria":"Бот воспроизводит expiry после PIT; игрок всегда имеет путь к restart-free recovery или таймер/геометрия исключают ловушку; сценарий проходит 3 раза подряд.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"closed","priority":0,"issue_type":"bug","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T22:09:40Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:42Z","started_at":"2026-08-17T22:33:08Z","closed_at":"2026-08-18T01:30:42Z","close_reason":"Expiry справа от PIT воспроизводился; recovery relay и баланс таймера проверены intended 3/3 и recovery 3/3.","labels":["bug","current","level8","softlock"],"dependencies":[{"issue_id":"goon-game-level8-softlock","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T01:09:48Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
|
||||||
{"_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-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-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-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 минут каждый.","notes":"2026-08-18: one final integrated Android 16 run is green: 14/14 methods, 35 gameplay executions plus lifecycle, Gradle wall time 5:59. Correctness is proven, but acceptance remains open because the \u003c5:00 target is missed by 59s and three full matrix repetitions are not yet recorded.","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-18T01:31:00Z","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-regression-10","title":"Единый playability regression 10/10","description":"Запускать всех ботов одной Gradle-командой и публиковать краткий отчёт по уровням.","acceptance_criteria":"Три полных последовательных прогона дают 10/10 passed менее чем за 5 минут каждый.","notes":"2026-08-18: one final integrated Android 16 run is green: 14/14 methods, 35 gameplay executions plus lifecycle, Gradle wall time 5:59. Correctness is proven, but acceptance remains open because the \u003c5:00 target is missed by 59s and three full matrix repetitions are not yet recorded.\n2026-08-18 final Stage 2 candidate: one exact unfiltered Android 16 run passed 16/16 methods, 36 gameplay executions plus lifecycle/restart in 479.619 s. Correctness gate is green; historical \u003c5:00 and three full exact-candidate repetitions remain open as runner-performance evidence.\n2026-08-18 exact versionCode 5 clean-build matrix: OK 16/16, 36 gameplay executions plus lifecycle/restart, 572.496 s, no retries. APK installed over previous build before the run.","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-18T10:52:43Z","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":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:49Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:46Z","started_at":"2026-08-17T22:33:08Z","closed_at":"2026-08-18T01:30:46Z","close_reason":"Уровни 6–10 проходят intended routes по 3 раза; уровни 8 и 10 дополнительно проходят 96ms stress checks.","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":"{}"},{"issue_id":"goon-game-bots-6-10","depends_on_id":"goon-game-level6-freezer-overlap","type":"blocks","created_at":"2026-08-18T01:47:00Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"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":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:49Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:46Z","started_at":"2026-08-17T22:33:08Z","closed_at":"2026-08-18T01:30:46Z","close_reason":"Уровни 6–10 проходят intended routes по 3 раза; уровни 8 и 10 дополнительно проходят 96ms stress checks.","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":"{}"},{"issue_id":"goon-game-bots-6-10","depends_on_id":"goon-game-level6-freezer-overlap","type":"blocks","created_at":"2026-08-18T01:47:00Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":2,"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":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:47Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:34Z","started_at":"2026-08-17T22:33:08Z","closed_at":"2026-08-18T01:30:34Z","close_reason":"Уровни 1–4 проходят production-input ботами по 3 раза подряд; финальный integrated Android 16 matrix 14/14 green.","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-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":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:47Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:30:34Z","started_at":"2026-08-17T22:33:08Z","closed_at":"2026-08-18T01:30:34Z","close_reason":"Уровни 1–4 проходят production-input ботами по 3 раза подряд; финальный integrated Android 16 matrix 14/14 green.","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":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:45Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:09:01Z","started_at":"2026-08-17T21:52:45Z","closed_at":"2026-08-17T22:09:01Z","close_reason":"Декларативный Scenario runner, именованные шаги, per-step timeout и observable diagnostics реализованы; 11 JVM-тестов, lint, instrumentation compile и connected level 5 GREEN на Android 16.","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-bot-framework","title":"Обобщить acceptance bot framework","description":"Выделить сценарные шаги, snapshots, диагностику таймаутов и общий runner для уровней 1–10.","acceptance_criteria":"Новый уровень подключается декларативным сценарием; ошибка показывает последний шаг и state.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:40:45Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:09:01Z","started_at":"2026-08-17T21:52:45Z","closed_at":"2026-08-17T22:09:01Z","close_reason":"Декларативный Scenario runner, именованные шаги, per-step timeout и observable diagnostics реализованы; 11 JVM-тестов, lint, instrumentation compile и connected level 5 GREEN на Android 16.","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}
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
{"_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-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":"Инструментальные тесты подтверждают паузу и восстановление поддерживаемого состояния.","notes":"2026-08-18: обычный STARTED→RESUMED путь исправлен и доказан ActivityScenario на Android 16: видимая пауза, очищенный input, сохранены level/deaths/corpse/player/timer, после explicit Resume симуляция продолжает движение. Connected GREEN 1/1. Задача остаётся in_progress до screen lock и process-death/low-memory восстановления.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:26Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:09:20Z","started_at":"2026-08-17T21:52:46Z","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-lifecycle","title":"Android lifecycle и восстановление Activity","description":"Pause/background/resume/screen lock/low memory без потери или скрытого продолжения игры.","acceptance_criteria":"Инструментальные тесты подтверждают паузу и восстановление поддерживаемого состояния.","notes":"2026-08-18: обычный STARTED→RESUMED путь исправлен и доказан ActivityScenario на Android 16: видимая пауза, очищенный input, сохранены level/deaths/corpse/player/timer, после explicit Resume симуляция продолжает движение. Connected GREEN 1/1. Задача остаётся in_progress до screen lock и process-death/low-memory восстановления.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1440,"created_at":"2026-08-17T21:40:26Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:09:20Z","started_at":"2026-08-17T21:52:46Z","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-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 уровней проходят три раза подряд и понятны внешнему тестировщику.","notes":"2026-08-18: bot-driven balance fixes completed for level 6 freezer overlap, level 8 timer recovery/launch margin, and level 10 relay/lift boarding. Intended routes 1–10 pass 3x in one integrated matrix. Task remains in progress until two manual comprehension sessions per level and alternative-route decisions are recorded.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:39:56Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T01:31:03Z","started_at":"2026-08-18T01:31:03Z","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-level-route-risks","type":"blocks","created_at":"2026-08-18T01:10:21Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-level-balance","depends_on_id":"goon-game-level8-softlock","type":"blocks","created_at":"2026-08-18T01:10:14Z","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":4,"dependent_count":1,"comment_count":0}
|
{"_type":"issue","id":"goon-game-level-balance","title":"Исправить стабильность и баланс уровней 1–10","description":"Устранить softlocks, нестабильные тела и чрезмерно точные прыжки, найденные ботами и ручным тестированием.","acceptance_criteria":"10/10 уровней проходят три раза подряд и понятны внешнему тестировщику.","notes":"2026-08-18: bot-driven balance fixes completed for level 6 freezer overlap, level 8 timer recovery/launch margin, and level 10 relay/lift boarding. Intended routes 1–10 pass 3x in one integrated matrix. Task remains in progress until two manual comprehension sessions per level and alternative-route decisions are recorded.\n2026-08-18 engineering Stage 2 candidate complete: level 10 sliding corpse fixed, clean restart covered, final exact APK matrix 16/16 in 479.619 s. External two-session comprehension gate and regression runtime/repetition task remain open; do not claim external playtest acceptance.\n2026-08-18 exact versionCode 5 full matrix also GREEN 16/16 in 572.496 s. Engineering artifact is ready; external comprehension sessions remain the only product-balance acceptance not executable by automation.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"in_progress","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":1920,"created_at":"2026-08-17T21:39:56Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T10:52:44Z","started_at":"2026-08-18T01:31:03Z","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-level-route-risks","type":"blocks","created_at":"2026-08-18T01:10:21Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-level-balance","depends_on_id":"goon-game-level10-sliding-corpse","type":"blocks","created_at":"2026-08-18T11:40:08Z","created_by":"E.Gavrilov","metadata":"{}"},{"issue_id":"goon-game-level-balance","depends_on_id":"goon-game-level8-softlock","type":"blocks","created_at":"2026-08-18T01:10:14Z","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":5,"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":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:39:54Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:09:09Z","started_at":"2026-08-17T21:52:46Z","closed_at":"2026-08-17T22:09:09Z","close_reason":"Документированы 10 source-backed solution graphs с механиками, смертями, checkpoints и рисками; только уровень 5 честно отмечен доказанным connected-тестом.","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-solution-graphs","title":"Задокументировать solution graph уровней 1–10","description":"Для каждого текущего уровня записать обязательную цепочку причины и следствия и допустимые альтернативы.","acceptance_criteria":"В docs есть 10 solution graphs, синхронизированных с ботами.","spec_id":"docs/DEVELOPMENT_PLAN.md","status":"closed","priority":0,"issue_type":"task","assignee":"E.Gavrilov","owner":"gavrilovdev@yandex-team.ru","estimated_minutes":960,"created_at":"2026-08-17T21:39:54Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:09:09Z","started_at":"2026-08-17T21:52:46Z","closed_at":"2026-08-17T22:09:09Z","close_reason":"Документированы 10 source-backed solution graphs с механиками, смертями, checkpoints и рисками; только уровень 5 честно отмечен доказанным connected-тестом.","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-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-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}
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
{"_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-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-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-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-level-route-risks","title":"Доказать спорные маршруты уровней 2, 4 и 10","description":"Проверить возможный sequence break уровня 2, узкий lift→gallery прыжок уровня 4 и optional FREEZER/body delivery/lift→ledge риски уровня 10.","acceptance_criteria":"Для каждого риска есть воспроизводимый connected bot test; intended route проходит 3 раза, а допустимые альтернативы явно зафиксированы или геометрия исправлена.","notes":"2026-08-18: exploratory production-input direct-jump attempt on level 2 did not confirm the suspected zero-death sequence break; it timed out at x=549.36 with deaths=2/corpses=2. Intended three-corpse route separately passes 3 times. Sequence-break risk remains unproven and open.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"open","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T22:09:58Z","created_by":"E.Gavrilov","updated_at":"2026-08-17T22:53:56Z","labels":["balance","bot","current","levels"],"dependencies":[{"issue_id":"goon-game-level-route-risks","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T01:10:06Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
|
{"_type":"issue","id":"goon-game-level-route-risks","title":"Доказать спорные маршруты уровней 2, 4 и 10","description":"Проверить возможный sequence break уровня 2, узкий lift→gallery прыжок уровня 4 и optional FREEZER/body delivery/lift→ledge риски уровня 10.","acceptance_criteria":"Для каждого риска есть воспроизводимый connected bot test; intended route проходит 3 раза, а допустимые альтернативы явно зафиксированы или геометрия исправлена.","notes":"2026-08-18: exploratory production-input direct-jump attempt on level 2 did not confirm the suspected zero-death sequence break; it timed out at x=549.36 with deaths=2/corpses=2. Intended three-corpse route separately passes 3 times. Sequence-break risk remains unproven and open.\n2026-08-18: L4 lift route remains green 3x. L10 bot now explicitly stands on and jumps from the frozen corpse, then completes normal 3x plus delayed traversal. L2 intended three-corpse route remains green 3x; the unconfirmed zero-death hypothesis is accepted as a non-blocking emergent alternative because targetDeaths measures the intended route rather than enforced uniqueness.","spec_id":"docs/LEVEL_SOLUTION_GRAPHS.md","status":"closed","priority":1,"issue_type":"task","owner":"gavrilovdev@yandex-team.ru","created_at":"2026-08-17T22:09:58Z","created_by":"E.Gavrilov","updated_at":"2026-08-18T10:41:48Z","closed_at":"2026-08-18T10:41:48Z","close_reason":"All intended risky routes are reproduced by connected bots; possible L2 alternative is explicitly accepted and documented as non-blocking.","labels":["balance","bot","current","levels"],"dependencies":[{"issue_id":"goon-game-level-route-risks","depends_on_id":"goon-game-e07","type":"parent-child","created_at":"2026-08-18T01:10:06Z","created_by":"E.Gavrilov","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"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-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-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-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}
|
||||||
|
|||||||
@@ -46,8 +46,12 @@ Requirements: JDK 17 and Android SDK Platform/Build Tools 34.
|
|||||||
All ten levels have device-level acceptance bots. They drive the production
|
All ten levels have device-level acceptance bots. They drive the production
|
||||||
`GameView` through the same movement and jump inputs as the touch controls and
|
`GameView` through the same movement and jump inputs as the touch controls and
|
||||||
run each intended solution three times. Level 8 also has a dedicated recovery
|
run each intended solution three times. Level 8 also has a dedicated recovery
|
||||||
scenario for an expired timer on the far side of the pit. With an emulator or
|
scenario for an expired timer on the far side of the pit. Stage 2 additionally
|
||||||
phone connected, run the complete matrix with:
|
checks clean restart and long-term stability of the saw-delivered body on the
|
||||||
|
level 10 lift plate. The final Stage 2 build passed all 16 instrumentation
|
||||||
|
methods (36 gameplay executions plus lifecycle and restart) on Android 16 in a
|
||||||
|
single unfiltered run. With an emulator or phone connected, run the complete
|
||||||
|
matrix with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./gradlew connectedDebugAndroidTest
|
./gradlew connectedDebugAndroidTest
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ android {
|
|||||||
applicationId 'com.projectsacrifice.game'
|
applicationId 'com.projectsacrifice.game'
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 4
|
versionCode 5
|
||||||
versionName '0.3.0-stage2-playability'
|
versionName '0.4.0-stage2-complete'
|
||||||
|
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.projectsacrifice.game;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class GameRestartTest {
|
||||||
|
@Test
|
||||||
|
public void restartClearsBodiesSignalsTimerAndHeldInput() throws Exception {
|
||||||
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||||
|
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
|
GameView[] view = new GameView[1];
|
||||||
|
activity.onActivity(current -> view[0] = current.gameViewForAutomation());
|
||||||
|
|
||||||
|
instrumentation.runOnMainSync(() -> {
|
||||||
|
view[0].startLevelForAutomation(10);
|
||||||
|
view[0].setAutomationInput(false, true, false);
|
||||||
|
});
|
||||||
|
await(instrumentation, view[0], 4_000L,
|
||||||
|
snapshot -> snapshot.deaths() == 1 && snapshot.corpseCount() == 1);
|
||||||
|
instrumentation.runOnMainSync(
|
||||||
|
() -> view[0].setAutomationInput(false, false, false));
|
||||||
|
await(instrumentation, view[0], 5_000L,
|
||||||
|
GameView.AutomationSnapshot::firstMovingPlatformPowered);
|
||||||
|
|
||||||
|
instrumentation.runOnMainSync(() -> {
|
||||||
|
view[0].setAutomationInput(false, true, true);
|
||||||
|
view[0].restartLevelForAutomation();
|
||||||
|
});
|
||||||
|
GameView.AutomationSnapshot restarted = snapshot(instrumentation, view[0]);
|
||||||
|
|
||||||
|
assertEquals(10, restarted.levelNumber());
|
||||||
|
assertTrue(restarted.playerAlive());
|
||||||
|
assertEquals(0, restarted.deaths());
|
||||||
|
assertEquals(0, restarted.corpseCount());
|
||||||
|
assertFalse(restarted.firstMovingPlatformPowered());
|
||||||
|
assertFalse(restarted.inputActive());
|
||||||
|
assertTrue("Restarted timer must begin near zero: "
|
||||||
|
+ restarted.elapsedMilliseconds(), restarted.elapsedMilliseconds() < 200L);
|
||||||
|
assertTrue("Restarted player must be at the level spawn: x=" + restarted.playerX(),
|
||||||
|
restarted.playerX() >= 125f && restarted.playerX() <= 135f);
|
||||||
|
assertEquals(790f, restarted.firstMovingPlatformY(), 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameView.AutomationSnapshot await(
|
||||||
|
Instrumentation instrumentation,
|
||||||
|
GameView view,
|
||||||
|
long timeoutMillis,
|
||||||
|
LevelPlaytestBot.SnapshotCondition condition
|
||||||
|
) throws Exception {
|
||||||
|
long deadline = System.currentTimeMillis() + timeoutMillis;
|
||||||
|
GameView.AutomationSnapshot current = snapshot(instrumentation, view);
|
||||||
|
while (System.currentTimeMillis() < deadline) {
|
||||||
|
current = snapshot(instrumentation, view);
|
||||||
|
if (condition.reached(current)) return current;
|
||||||
|
Thread.sleep(32L);
|
||||||
|
}
|
||||||
|
throw new AssertionError("Restart setup timed out; level=" + current.levelNumber()
|
||||||
|
+ ", x=" + current.playerX() + ", deaths=" + current.deaths()
|
||||||
|
+ ", corpses=" + current.corpseCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameView.AutomationSnapshot snapshot(
|
||||||
|
Instrumentation instrumentation,
|
||||||
|
GameView view
|
||||||
|
) {
|
||||||
|
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
|
||||||
|
instrumentation.runOnMainSync(() -> value[0] = view.automationSnapshot());
|
||||||
|
return value[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,6 +126,63 @@ public final class LateLevelPlaythroughTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sawDeliveredBodyKeepsLevelTenLiftPoweredAfterSettling() {
|
||||||
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||||
|
GameDriver driver = GameDriver.from(activity);
|
||||||
|
driver.startLevel(10);
|
||||||
|
await(driver, 4_000L, false, true, false,
|
||||||
|
snapshot -> snapshot.deaths() == 1 && snapshot.corpseCount() == 1);
|
||||||
|
GameView.AutomationSnapshot engaged = await(driver, 5_000L, false, false, false,
|
||||||
|
GameView.AutomationSnapshot::firstMovingPlatformPowered);
|
||||||
|
driver.setInput(false, false, false);
|
||||||
|
driver.waitForNextDecision(750L);
|
||||||
|
|
||||||
|
GameView.AutomationSnapshot settled = driver.snapshot();
|
||||||
|
assertEquals("The body must settle when it engages the lift plate instead of sliding",
|
||||||
|
engaged.firstCorpseX(), settled.firstCorpseX(), 2f);
|
||||||
|
|
||||||
|
driver.waitForNextDecision(6_000L);
|
||||||
|
|
||||||
|
settled = driver.snapshot();
|
||||||
|
assertTrue("The saw-delivered body slid off the lift plate after settling; "
|
||||||
|
+ "platformY=" + settled.firstMovingPlatformY()
|
||||||
|
+ ", deaths=" + settled.deaths()
|
||||||
|
+ ", corpses=" + settled.corpseCount(),
|
||||||
|
settled.firstMovingPlatformPowered());
|
||||||
|
assertEquals("The stability wait must not consume another volunteer",
|
||||||
|
1, settled.deaths());
|
||||||
|
assertEquals("The delivered body must remain in the level",
|
||||||
|
1, settled.corpseCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameView.AutomationSnapshot await(
|
||||||
|
GameDriver driver,
|
||||||
|
long timeoutMillis,
|
||||||
|
boolean moveLeft,
|
||||||
|
boolean moveRight,
|
||||||
|
boolean jump,
|
||||||
|
LevelPlaytestBot.SnapshotCondition condition
|
||||||
|
) {
|
||||||
|
long deadline = System.currentTimeMillis() + timeoutMillis;
|
||||||
|
GameView.AutomationSnapshot snapshot = driver.snapshot();
|
||||||
|
while (System.currentTimeMillis() < deadline) {
|
||||||
|
snapshot = driver.snapshot();
|
||||||
|
if (condition.reached(snapshot)) {
|
||||||
|
driver.setInput(false, false, false);
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
driver.setInput(moveLeft, moveRight, jump);
|
||||||
|
driver.waitForNextDecision(32L);
|
||||||
|
}
|
||||||
|
throw new AssertionError("Timed out waiting for level 10 lift body; x="
|
||||||
|
+ snapshot.playerX() + ", y=" + snapshot.playerY()
|
||||||
|
+ ", deaths=" + snapshot.deaths()
|
||||||
|
+ ", corpses=" + snapshot.corpseCount()
|
||||||
|
+ ", platformY=" + snapshot.firstMovingPlatformY());
|
||||||
|
}
|
||||||
|
|
||||||
private static final class GameDriver implements LevelPlaytestBot.Driver {
|
private static final class GameDriver implements LevelPlaytestBot.Driver {
|
||||||
private final Instrumentation instrumentation;
|
private final Instrumentation instrumentation;
|
||||||
private final GameView gameView;
|
private final GameView gameView;
|
||||||
|
|||||||
@@ -156,10 +156,27 @@ final class LateLevelScenarios {
|
|||||||
.step("Clear the lift body again", 2_000L,
|
.step("Clear the lift body again", 2_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
|
||||||
.step("Prepare the frozen-block jump", 2_000L,
|
.step("Release after the lift-body jump", 500L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
|
snapshot -> !snapshot.inputActive())
|
||||||
.step("Jump over the frozen block", 2_500L,
|
.step("Land after the lift-body jump", 1_500L,
|
||||||
|
LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerAlive() && snapshot.playerOnGround())
|
||||||
|
.step("Back away from the freezer", 1_000L,
|
||||||
|
LevelPlaytestBot.Input.left(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerX() <= 960f)
|
||||||
|
.step("Jump onto the frozen block", 2_000L,
|
||||||
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerX() >= 1_040f
|
||||||
|
&& snapshot.playerY() <= 700f)
|
||||||
|
.step("Settle on the frozen block", 1_500L,
|
||||||
|
LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerOnGround()
|
||||||
|
&& snapshot.playerY() <= 690f)
|
||||||
|
.step("Jump from the frozen block", 2_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
||||||
.step("Power the final relay", 3_000L, LevelPlaytestBot.Input.right(),
|
.step("Power the final relay", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
@@ -186,12 +203,34 @@ final class LateLevelScenarios {
|
|||||||
.step("Clear the retained lift body", 2_000L,
|
.step("Clear the retained lift body", 2_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
|
||||||
.step("Prepare the final frozen-block jump", 2_000L,
|
.step("Release after the retained-body jump", 500L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
|
snapshot -> !snapshot.inputActive())
|
||||||
.step("Clear the frozen block", 2_500L,
|
.step("Land after the retained-body jump", 1_500L,
|
||||||
|
LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerAlive() && snapshot.playerOnGround())
|
||||||
|
.step("Back away from the retained freezer", 1_000L,
|
||||||
|
LevelPlaytestBot.Input.left(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerX() <= 960f)
|
||||||
|
.step("Jump onto the retained frozen block", 2_000L,
|
||||||
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerX() >= 1_040f
|
||||||
|
&& snapshot.playerY() <= 700f)
|
||||||
|
.step("Settle on the retained frozen block", 1_500L,
|
||||||
|
LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerOnGround()
|
||||||
|
&& snapshot.playerY() <= 690f)
|
||||||
|
.step("Jump from the retained frozen block", 2_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
||||||
|
.step("Land before the powered relay", 1_500L,
|
||||||
|
LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerOnGround()
|
||||||
|
&& snapshot.playerY() >= 779f)
|
||||||
.step("Approach the powered relay", 2_500L,
|
.step("Approach the powered relay", 2_500L,
|
||||||
LevelPlaytestBot.Input.right(),
|
LevelPlaytestBot.Input.right(),
|
||||||
snapshot -> snapshot.playerX() >= 1_320f)
|
snapshot -> snapshot.playerX() >= 1_320f)
|
||||||
@@ -201,15 +240,23 @@ final class LateLevelScenarios {
|
|||||||
.step("Clear the powered relay", 2_000L,
|
.step("Clear the powered relay", 2_000L,
|
||||||
LevelPlaytestBot.Input.right(),
|
LevelPlaytestBot.Input.right(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_650f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_650f)
|
||||||
.step("Wait for the lift at floor height", 6_000L,
|
.step("Observe the lift above the launch floor", 6_000L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerAlive()
|
snapshot -> snapshot.playerAlive()
|
||||||
&& snapshot.playerY() >= 770f
|
|
||||||
&& !Float.isNaN(snapshot.firstMovingPlatformY())
|
&& !Float.isNaN(snapshot.firstMovingPlatformY())
|
||||||
&& snapshot.firstMovingPlatformY() >= 780f)
|
&& snapshot.firstMovingPlatformY() <= 650f)
|
||||||
|
.step("Wait for the lift at floor height", 9_000L,
|
||||||
|
LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& (snapshot.playerOnMovingPlatform()
|
||||||
|
|| (snapshot.playerY() >= 770f
|
||||||
|
&& !Float.isNaN(snapshot.firstMovingPlatformY())
|
||||||
|
&& snapshot.firstMovingPlatformY() >= 780f)))
|
||||||
.step("Take off toward the moving lift", 1_000L,
|
.step("Take off toward the moving lift", 1_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 670f)
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& (snapshot.playerOnMovingPlatform()
|
||||||
|
|| snapshot.playerY() <= 670f))
|
||||||
.step("Board the moving lift", 2_000L,
|
.step("Board the moving lift", 2_000L,
|
||||||
LevelPlaytestBot.Input.right(),
|
LevelPlaytestBot.Input.right(),
|
||||||
GameView.AutomationSnapshot::playerOnMovingPlatform)
|
GameView.AutomationSnapshot::playerOnMovingPlatform)
|
||||||
@@ -221,7 +268,7 @@ final class LateLevelScenarios {
|
|||||||
LevelPlaytestBot.Input.right(),
|
LevelPlaytestBot.Input.right(),
|
||||||
snapshot -> snapshot.playerOnMovingPlatform()
|
snapshot -> snapshot.playerOnMovingPlatform()
|
||||||
&& snapshot.playerX() >= 1_920f)
|
&& snapshot.playerX() >= 1_920f)
|
||||||
.step("Ride the lift to the upper door", 5_000L,
|
.step("Ride the lift to the upper door", 7_000L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerY() <= 310f)
|
snapshot -> snapshot.playerY() <= 310f)
|
||||||
.step("Move to the raised lift edge", 2_000L,
|
.step("Move to the raised lift edge", 2_000L,
|
||||||
@@ -236,4 +283,5 @@ final class LateLevelScenarios {
|
|||||||
GameView.AutomationSnapshot::completed)
|
GameView.AutomationSnapshot::completed)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ final class LevelEightScenario {
|
|||||||
|
|
||||||
snapshot = await(driver, deadline, "Wait for the moving platform",
|
snapshot = await(driver, deadline, "Wait for the moving platform",
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
current -> current.elapsedMilliseconds() >= timerStartedAt + 2_250L);
|
current -> current.elapsedMilliseconds() >= timerStartedAt + 2_250L
|
||||||
|
&& current.firstMovingPlatformX() <= 1_050f);
|
||||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||||
|
|
||||||
driver.setInput(false, true, true);
|
driver.setInput(false, true, true);
|
||||||
@@ -87,7 +88,7 @@ final class LevelEightScenario {
|
|||||||
driver.setInput(false, true, false);
|
driver.setInput(false, true, false);
|
||||||
snapshot = await(driver, deadline, "Land on the moving platform",
|
snapshot = await(driver, deadline, "Land on the moving platform",
|
||||||
LevelPlaytestBot.Input.right(),
|
LevelPlaytestBot.Input.right(),
|
||||||
current -> current.elapsedMilliseconds() >= timerStartedAt + 3_200L);
|
GameView.AutomationSnapshot::playerOnMovingPlatform);
|
||||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||||
|
|
||||||
driver.setInput(false, true, true);
|
driver.setInput(false, true, true);
|
||||||
@@ -107,18 +108,15 @@ final class LevelEightScenario {
|
|||||||
private GameView.AutomationSnapshot awaitFarFloorLandingOnOriginalTimer(
|
private GameView.AutomationSnapshot awaitFarFloorLandingOnOriginalTimer(
|
||||||
LevelPlaytestBot.Driver driver, long deadline
|
LevelPlaytestBot.Driver driver, long deadline
|
||||||
) {
|
) {
|
||||||
boolean moveLeft = true;
|
GameView.AutomationSnapshot snapshot = await(driver, deadline,
|
||||||
GameView.AutomationSnapshot snapshot = driver.snapshot();
|
"Land on the far floor", LevelPlaytestBot.Input.right(),
|
||||||
while (driver.currentTimeMillis() < deadline && snapshot.playerAlive()
|
current -> current.playerX() >= 1_510f
|
||||||
&& !(snapshot.playerX() >= 1_510f && snapshot.playerX() < 1_580f
|
&& current.playerY() >= 779f && current.allDoorsOpen());
|
||||||
&& snapshot.playerY() >= 779f && snapshot.allDoorsOpen())) {
|
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||||
driver.setInput(moveLeft, !moveLeft, false);
|
return await(driver, deadline, "Stabilize on the far floor",
|
||||||
moveLeft = !moveLeft;
|
LevelPlaytestBot.Input.left(),
|
||||||
driver.waitForNextDecision(DECISION_MILLIS);
|
current -> current.playerX() < 1_580f
|
||||||
snapshot = driver.snapshot();
|
&& current.playerY() >= 779f && current.allDoorsOpen());
|
||||||
}
|
|
||||||
driver.setInput(false, false, false);
|
|
||||||
return snapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result finishFromFarSide(LevelPlaytestBot.Driver driver, long deadline,
|
private Result finishFromFarSide(LevelPlaytestBot.Driver driver, long deadline,
|
||||||
|
|||||||
@@ -53,8 +53,12 @@ public final class GameView extends View implements Runnable {
|
|||||||
private final float playerY;
|
private final float playerY;
|
||||||
private final int deaths;
|
private final int deaths;
|
||||||
private final int corpseCount;
|
private final int corpseCount;
|
||||||
|
private final float firstMovingPlatformX;
|
||||||
private final float firstMovingPlatformY;
|
private final float firstMovingPlatformY;
|
||||||
private final boolean playerOnMovingPlatform;
|
private final boolean playerOnMovingPlatform;
|
||||||
|
private final boolean firstMovingPlatformPowered;
|
||||||
|
private final float firstCorpseX;
|
||||||
|
private final boolean playerOnGround;
|
||||||
|
|
||||||
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
||||||
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
||||||
@@ -62,14 +66,17 @@ public final class GameView extends View implements Runnable {
|
|||||||
float playerX, float playerY, int deaths, int corpseCount) {
|
float playerX, float playerY, int deaths, int corpseCount) {
|
||||||
this(completed, playerAlive, allDoorsOpen, visiblyPaused, inputActive, levelNumber,
|
this(completed, playerAlive, allDoorsOpen, visiblyPaused, inputActive, levelNumber,
|
||||||
elapsedMilliseconds, playerX, playerY, deaths, corpseCount,
|
elapsedMilliseconds, playerX, playerY, deaths, corpseCount,
|
||||||
Float.NaN, false);
|
Float.NaN, Float.NaN, false, false, Float.NaN, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
||||||
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
||||||
long elapsedMilliseconds,
|
long elapsedMilliseconds,
|
||||||
float playerX, float playerY, int deaths, int corpseCount,
|
float playerX, float playerY, int deaths, int corpseCount,
|
||||||
float firstMovingPlatformY, boolean playerOnMovingPlatform) {
|
float firstMovingPlatformX, float firstMovingPlatformY,
|
||||||
|
boolean playerOnMovingPlatform,
|
||||||
|
boolean firstMovingPlatformPowered, float firstCorpseX,
|
||||||
|
boolean playerOnGround) {
|
||||||
this.completed = completed;
|
this.completed = completed;
|
||||||
this.playerAlive = playerAlive;
|
this.playerAlive = playerAlive;
|
||||||
this.allDoorsOpen = allDoorsOpen;
|
this.allDoorsOpen = allDoorsOpen;
|
||||||
@@ -81,8 +88,12 @@ public final class GameView extends View implements Runnable {
|
|||||||
this.playerY = playerY;
|
this.playerY = playerY;
|
||||||
this.deaths = deaths;
|
this.deaths = deaths;
|
||||||
this.corpseCount = corpseCount;
|
this.corpseCount = corpseCount;
|
||||||
|
this.firstMovingPlatformX = firstMovingPlatformX;
|
||||||
this.firstMovingPlatformY = firstMovingPlatformY;
|
this.firstMovingPlatformY = firstMovingPlatformY;
|
||||||
this.playerOnMovingPlatform = playerOnMovingPlatform;
|
this.playerOnMovingPlatform = playerOnMovingPlatform;
|
||||||
|
this.firstMovingPlatformPowered = firstMovingPlatformPowered;
|
||||||
|
this.firstCorpseX = firstCorpseX;
|
||||||
|
this.playerOnGround = playerOnGround;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean completed() { return completed; }
|
public boolean completed() { return completed; }
|
||||||
@@ -96,8 +107,12 @@ public final class GameView extends View implements Runnable {
|
|||||||
public float playerY() { return playerY; }
|
public float playerY() { return playerY; }
|
||||||
public int deaths() { return deaths; }
|
public int deaths() { return deaths; }
|
||||||
public int corpseCount() { return corpseCount; }
|
public int corpseCount() { return corpseCount; }
|
||||||
|
public float firstMovingPlatformX() { return firstMovingPlatformX; }
|
||||||
public float firstMovingPlatformY() { return firstMovingPlatformY; }
|
public float firstMovingPlatformY() { return firstMovingPlatformY; }
|
||||||
public boolean playerOnMovingPlatform() { return playerOnMovingPlatform; }
|
public boolean playerOnMovingPlatform() { return playerOnMovingPlatform; }
|
||||||
|
public boolean firstMovingPlatformPowered() { return firstMovingPlatformPowered; }
|
||||||
|
public float firstCorpseX() { return firstCorpseX; }
|
||||||
|
public boolean playerOnGround() { return playerOnGround; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Player {
|
private static final class Player {
|
||||||
@@ -235,6 +250,8 @@ public final class GameView extends View implements Runnable {
|
|||||||
respawnTimer = 0f;
|
respawnTimer = 0f;
|
||||||
cameraX = 0f;
|
cameraX = 0f;
|
||||||
userPaused = false;
|
userPaused = false;
|
||||||
|
pointers.clear();
|
||||||
|
deriveControls();
|
||||||
corpses.clear();
|
corpses.clear();
|
||||||
platformStates.clear();
|
platformStates.clear();
|
||||||
toggleSignals.clear();
|
toggleSignals.clear();
|
||||||
@@ -465,11 +482,29 @@ public final class GameView extends View implements Runnable {
|
|||||||
resolveCorpseHorizontal(corpse);
|
resolveCorpseHorizontal(corpse);
|
||||||
corpse.y += corpse.vy * dt;
|
corpse.y += corpse.vy * dt;
|
||||||
resolveCorpseVertical(corpse);
|
resolveCorpseVertical(corpse);
|
||||||
|
if (corpse.grounded && settleCorpseOnPressurePlate(corpse)) continue;
|
||||||
if (corpse.grounded) corpse.vx *= (float) Math.pow(0.05, dt);
|
if (corpse.grounded) corpse.vx *= (float) Math.pow(0.05, dt);
|
||||||
if (Math.abs(corpse.vx) < 4f) corpse.vx = 0f;
|
if (Math.abs(corpse.vx) < 4f) corpse.vx = 0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean settleCorpseOnPressurePlate(Corpse corpse) {
|
||||||
|
for (LevelDefinition.Switch item : level.switches) {
|
||||||
|
if (item.type != LevelDefinition.SwitchType.PRESSURE) continue;
|
||||||
|
if (!intersects(corpse.x, corpse.y, corpse.width, corpse.height, item.box)) continue;
|
||||||
|
if (item.box.width >= corpse.width) {
|
||||||
|
corpse.x = GameMath.clamp(corpse.x, item.box.x, item.box.right() - corpse.width);
|
||||||
|
} else {
|
||||||
|
corpse.x = item.box.x + (item.box.width - corpse.width) * 0.5f;
|
||||||
|
}
|
||||||
|
corpse.vx = 0f;
|
||||||
|
corpse.vy = 0f;
|
||||||
|
corpse.pinned = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void resolveCorpseHorizontal(Corpse corpse) {
|
private void resolveCorpseHorizontal(Corpse corpse) {
|
||||||
RectF current = corpse.rect();
|
RectF current = corpse.rect();
|
||||||
for (RectF solid : solidRects(false)) {
|
for (RectF solid : solidRects(false)) {
|
||||||
@@ -1131,6 +1166,10 @@ public final class GameView extends View implements Runnable {
|
|||||||
startLevel(oneBasedLevelNumber - 1);
|
startLevel(oneBasedLevelNumber - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void restartLevelForAutomation() {
|
||||||
|
restartLevel();
|
||||||
|
}
|
||||||
|
|
||||||
public void setAutomationInput(boolean moveLeft, boolean moveRight, boolean jump) {
|
public void setAutomationInput(boolean moveLeft, boolean moveRight, boolean jump) {
|
||||||
boolean jumpWasPressed = jumpPressed;
|
boolean jumpWasPressed = jumpPressed;
|
||||||
leftPressed = moveLeft;
|
leftPressed = moveLeft;
|
||||||
@@ -1144,10 +1183,14 @@ public final class GameView extends View implements Runnable {
|
|||||||
if (level != null) {
|
if (level != null) {
|
||||||
for (LevelDefinition.Door door : level.doors) allOpen &= doorOpen(door);
|
for (LevelDefinition.Door door : level.doors) allOpen &= doorOpen(door);
|
||||||
}
|
}
|
||||||
|
float firstMovingPlatformX = Float.NaN;
|
||||||
float firstMovingPlatformY = Float.NaN;
|
float firstMovingPlatformY = Float.NaN;
|
||||||
|
boolean firstMovingPlatformPowered = false;
|
||||||
for (PlatformState state : platformStates) {
|
for (PlatformState state : platformStates) {
|
||||||
if (state.definition.isMoving()) {
|
if (state.definition.isMoving()) {
|
||||||
|
firstMovingPlatformX = state.x;
|
||||||
firstMovingPlatformY = state.y;
|
firstMovingPlatformY = state.y;
|
||||||
|
firstMovingPlatformPowered = signalActive(state.definition.controlId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1167,8 +1210,12 @@ public final class GameView extends View implements Runnable {
|
|||||||
player.y,
|
player.y,
|
||||||
deaths,
|
deaths,
|
||||||
corpses.size(),
|
corpses.size(),
|
||||||
|
firstMovingPlatformX,
|
||||||
firstMovingPlatformY,
|
firstMovingPlatformY,
|
||||||
playerOnMovingPlatform
|
playerOnMovingPlatform,
|
||||||
|
firstMovingPlatformPowered,
|
||||||
|
corpses.isEmpty() ? Float.NaN : corpses.get(0).x,
|
||||||
|
player.onGround
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,12 +109,12 @@ public final class LevelCatalog {
|
|||||||
8, "Borrowed Time", "Jump for the far blue relay if the timed gate closes.",
|
8, "Borrowed Time", "Jump for the far blue relay if the timed gate closes.",
|
||||||
2450, 130, 760, goal(2220, 600), 0, 38,
|
2450, 130, 760, goal(2220, 600), 0, 38,
|
||||||
platforms(
|
platforms(
|
||||||
floor(0, 970), floor(1510, 940), ledge(1980, 710, 470),
|
floor(0, 1040), floor(1510, 940), ledge(1980, 710, 470),
|
||||||
moving(990, 770, 240, 34, 380, 0, 4.0f, "clock")
|
moving(910, 770, 320, 34, 380, 0, 4.0f, "clock")
|
||||||
),
|
),
|
||||||
hazards(hazard(PIT, 970, 880, 540, 200)),
|
hazards(hazard(PIT, 1040, 880, 470, 200)),
|
||||||
switches(
|
switches(
|
||||||
sw(TIMED, 560, 810, 90, 70, "clock", 6.0f, 1),
|
sw(TIMED, 560, 810, 90, 70, "clock", 8.0f, 1),
|
||||||
sw(TIMED, 1600, 650, 90, 70, "clock", 3.5f, 1)
|
sw(TIMED, 1600, 650, 90, 70, "clock", 3.5f, 1)
|
||||||
),
|
),
|
||||||
doors(door(1770, 560, 82, 320, "clock"))
|
doors(door(1770, 560, 82, 320, "clock"))
|
||||||
@@ -141,11 +141,11 @@ public final class LevelCatalog {
|
|||||||
3100, 130, 760, goal(2880, 330), 3, 120,
|
3100, 130, 760, goal(2880, 330), 3, 120,
|
||||||
platforms(
|
platforms(
|
||||||
floor(0, 1940), floor(2140, 960), ledge(2420, 440, 680),
|
floor(0, 1940), floor(2140, 960), ledge(2420, 440, 680),
|
||||||
moving(1840, 790, 250, 34, 0, -410, 5.0f, "lift")
|
moving(1800, 790, 290, 34, 0, -410, 10.0f, "lift")
|
||||||
),
|
),
|
||||||
hazards(
|
hazards(
|
||||||
hazard(SAW, 520, 760, 96, 96, null, 700, -240),
|
hazard(SAW, 520, 760, 96, 96, null, 700, -240),
|
||||||
hazard(FREEZER, 1110, 750, 100, 130),
|
hazard(FREEZER, 1110, 776, 100, 104),
|
||||||
hazard(ELECTRIC, 1510, 780, 110, 100, "power", 0, 0),
|
hazard(ELECTRIC, 1510, 780, 110, 100, "power", 0, 0),
|
||||||
hazard(PIT, 1940, 880, 200, 200)
|
hazard(PIT, 1940, 880, 200, 200)
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ public final class LevelPlaytestBot {
|
|||||||
Step step = scenario.steps.get(stepIndex);
|
Step step = scenario.steps.get(stepIndex);
|
||||||
lastStep = step.name;
|
lastStep = step.name;
|
||||||
if (step.completion.reached(last)) {
|
if (step.completion.reached(last)) {
|
||||||
|
driver.setInput(false, false, false);
|
||||||
stepIndex++;
|
stepIndex++;
|
||||||
stepStartedAt = driver.currentTimeMillis();
|
stepStartedAt = driver.currentTimeMillis();
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -100,6 +100,24 @@ public final class LevelCatalogTest {
|
|||||||
freezer.box.x, freezer.box.y, freezer.box.width, freezer.box.height));
|
freezer.box.x, freezer.box.y, freezer.box.width, freezer.box.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void masteryFreezerKillsOnTheFloorButNotOnTopOfItsFrozenBlock() {
|
||||||
|
LevelDefinition level = LevelCatalog.get(9);
|
||||||
|
LevelDefinition.Hazard freezer = null;
|
||||||
|
for (LevelDefinition.Hazard hazard : level.hazards) {
|
||||||
|
if (hazard.type == LevelDefinition.HazardType.FREEZER) freezer = hazard;
|
||||||
|
}
|
||||||
|
assertTrue("Level 10 must contain its freezer", freezer != null);
|
||||||
|
|
||||||
|
float frozenBlockTop = 880f - 104f;
|
||||||
|
assertTrue("A floor-running volunteer must still hit the mastery freezer",
|
||||||
|
GameMath.intersects(1_108f, 780f, 62f, 100f,
|
||||||
|
freezer.box.x, freezer.box.y, freezer.box.width, freezer.box.height));
|
||||||
|
assertFalse("A volunteer standing on the mastery frozen block must clear the freezer",
|
||||||
|
GameMath.intersects(1_108f, frozenBlockTop - 100f, 62f, 100f,
|
||||||
|
freezer.box.x, freezer.box.y, freezer.box.width, freezer.box.height));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void masteryLiftHasLaunchOverlapAndStillSpansARealPit() {
|
public void masteryLiftHasLaunchOverlapAndStillSpansARealPit() {
|
||||||
LevelDefinition level = LevelCatalog.get(9);
|
LevelDefinition level = LevelCatalog.get(9);
|
||||||
@@ -118,17 +136,40 @@ public final class LevelCatalogTest {
|
|||||||
|
|
||||||
float launchOverlap = Math.min(launchFloor.box.right(), lift.box.right())
|
float launchOverlap = Math.min(launchFloor.box.right(), lift.box.right())
|
||||||
- Math.max(launchFloor.box.x, lift.box.x);
|
- Math.max(launchFloor.box.x, lift.box.x);
|
||||||
assertTrue("The floor must overlap the lift by at least 100px for a stable launch",
|
assertTrue("The floor must overlap the lift by at least 140px for a delayed launch",
|
||||||
launchOverlap >= 100f);
|
launchOverlap >= 140f);
|
||||||
assertEquals("The pit must begin exactly where the launch floor ends",
|
assertEquals("The pit must begin exactly where the launch floor ends",
|
||||||
launchFloor.box.right(), pit.box.x, 0f);
|
launchFloor.box.right(), pit.box.x, 0f);
|
||||||
assertEquals("The pit must continue to the landing floor",
|
assertEquals("The pit must continue to the landing floor",
|
||||||
landingFloor.box.x, pit.box.right(), 0f);
|
landingFloor.box.x, pit.box.right(), 0f);
|
||||||
assertTrue("The final pit must remain at least 200px wide", pit.box.width >= 200f);
|
assertTrue("The final pit must remain at least 200px wide", pit.box.width >= 200f);
|
||||||
|
assertTrue("The mastery lift cycle must leave a readable delayed-input boarding window",
|
||||||
|
lift.periodSeconds >= 10f);
|
||||||
assertTrue("The upper gallery must still require the vertical lift",
|
assertTrue("The upper gallery must still require the vertical lift",
|
||||||
lift.travelY <= -400f && level.goal.y < launchFloor.box.y - 300f);
|
lift.travelY <= -400f && level.goal.y < launchFloor.box.y - 300f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void timedPlatformHasLaunchOverlapAndStillSpansARealPit() {
|
||||||
|
LevelDefinition level = LevelCatalog.get(7);
|
||||||
|
LevelDefinition.Platform launchFloor = level.platforms.get(0);
|
||||||
|
LevelDefinition.Platform landingFloor = level.platforms.get(1);
|
||||||
|
LevelDefinition.Platform movingPlatform = level.platforms.get(3);
|
||||||
|
LevelDefinition.Hazard pit = level.hazards.get(0);
|
||||||
|
|
||||||
|
float launchOverlap = Math.min(launchFloor.box.right(), movingPlatform.box.right())
|
||||||
|
- Math.max(launchFloor.box.x, movingPlatform.box.x);
|
||||||
|
assertTrue("The timed platform must overlap the launch floor by at least 50px",
|
||||||
|
launchOverlap >= 50f);
|
||||||
|
assertTrue("The timed platform needs a 320px left-side catch window",
|
||||||
|
movingPlatform.box.width >= 320f);
|
||||||
|
assertEquals("Widening must preserve the original right edge for recovery safety",
|
||||||
|
1_230f, movingPlatform.box.right(), 0f);
|
||||||
|
assertEquals(launchFloor.box.right(), pit.box.x, 0f);
|
||||||
|
assertEquals(landingFloor.box.x, pit.box.right(), 0f);
|
||||||
|
assertTrue("Borrowed Time must retain a real platforming pit", pit.box.width >= 450f);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void everyDoorAndControlledPlatformHasADeclaredSignalSource() {
|
public void everyDoorAndControlledPlatformHasADeclaredSignalSource() {
|
||||||
for (LevelDefinition level : LevelCatalog.all()) {
|
for (LevelDefinition level : LevelCatalog.all()) {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public final class LevelPlaytestBotTest {
|
|||||||
assertTrue(result.diagnostic(), result.completed());
|
assertTrue(result.diagnostic(), result.completed());
|
||||||
assertEquals(7, driver.startedLevel);
|
assertEquals(7, driver.startedLevel);
|
||||||
assertEquals("Reach the goal", result.lastStep());
|
assertEquals("Reach the goal", result.lastStep());
|
||||||
|
assertTrue("Completed steps must release held controls before the next action",
|
||||||
|
driver.inputReleases >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -98,6 +100,7 @@ public final class LevelPlaytestBotTest {
|
|||||||
private int deaths;
|
private int deaths;
|
||||||
private boolean completed;
|
private boolean completed;
|
||||||
private final boolean progresses;
|
private final boolean progresses;
|
||||||
|
private int inputReleases;
|
||||||
|
|
||||||
ScriptedDriver() {
|
ScriptedDriver() {
|
||||||
this(true);
|
this(true);
|
||||||
@@ -119,6 +122,7 @@ public final class LevelPlaytestBotTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setInput(boolean moveLeft, boolean moveRight, boolean jump) {
|
public void setInput(boolean moveLeft, boolean moveRight, boolean jump) {
|
||||||
|
if (!moveLeft && !moveRight && !jump) inputReleases++;
|
||||||
this.moveLeft = moveLeft;
|
this.moveLeft = moveLeft;
|
||||||
this.moveRight = moveRight;
|
this.moveRight = moveRight;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ master tracks origin/master
|
|||||||
- предметные задачи с acceptance criteria, приоритетами и зависимостями;
|
- предметные задачи с acceptance criteria, приоритетами и зависимостями;
|
||||||
- уже подтверждённые кодом и QA элементы закрыты с причиной.
|
- уже подтверждённые кодом и QA элементы закрыты с причиной.
|
||||||
|
|
||||||
На момент инициализации создано 68 beads: 13 закрыты, 4 находятся в работе и 51 открыт. Эти числа нельзя трактовать как точный процент готовности, потому что в базе смешаны эпики, features и задачи разного размера.
|
На 18 августа 2026 года в snapshot находятся 72 beads: 21 закрыт, 6 находятся в работе и 45 открыты. Эти числа нельзя трактовать как точный процент готовности, потому что в базе смешаны эпики, features и задачи разного размера.
|
||||||
|
|
||||||
Текущий gate — `goon-game-milestone-vslice`. Первая приоритетная незаблокированная задача — `goon-game-bot-framework`.
|
Текущий gate — `goon-game-milestone-vslice`. Инженерный кандидат этапа 2 собран; незакрытые gates включают `goon-game-human-playtest` и performance/repetition criteria `goon-game-regression-10`. Задачи этапа 3 можно начинать параллельно, не объявляя внешний playtest завершённым.
|
||||||
|
|
||||||
## Ежедневные команды
|
## Ежедневные команды
|
||||||
|
|
||||||
|
|||||||
+9
-14
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
## 1. Текущее состояние
|
## 1. Текущее состояние
|
||||||
|
|
||||||
Проект находится на стадии проверенного vertical slice:
|
Инженерная часть этапа 2 завершена и проект готов к параллельному старту этапа 3:
|
||||||
|
|
||||||
- реализованы 10 последовательных уровней;
|
- реализованы 10 последовательных уровней;
|
||||||
- работают движение, прыжок и мобильное multitouch-управление;
|
- работают движение, прыжок и мобильное multitouch-управление;
|
||||||
@@ -16,12 +16,14 @@
|
|||||||
- intended route всех уровней 1–10 защищён Android acceptance-ботами: каждый маршрут выполняется по три раза через production-ввод без прямого перемещения персонажа;
|
- intended route всех уровней 1–10 защищён Android acceptance-ботами: каждый маршрут выполняется по три раза через production-ввод без прямого перемещения персонажа;
|
||||||
- blocker-softlock уровня 8 после истечения таймера устранён recovery relay и покрыт отдельным трёхкратным сценарием;
|
- blocker-softlock уровня 8 после истечения таймера устранён recovery relay и покрыт отдельным трёхкратным сценарием;
|
||||||
- исправлено перекрытие `FREEZER` уровня 6, которое повторно убивало игрока на frozen stepping stone;
|
- исправлено перекрытие `FREEZER` уровня 6, которое повторно убивало игрока на frozen stepping stone;
|
||||||
- final integrated Android 16 matrix: 14/14 методов, 35 игровых выполнений и lifecycle-проверка; wall time `5:59`;
|
- тело, доставленное пилой на `PRESSURE` уровня 10, фиксируется после оседания и больше не соскальзывает с плиты;
|
||||||
|
- restart из промежуточного состояния уровня 10 очищает тела, сигналы, таймер и удерживаемый ввод;
|
||||||
|
- final integrated Android 16 matrix: 16/16 методов, 36 игровых выполнений, lifecycle и restart; wall time `9:32.496`;
|
||||||
- финальный debug APK проверен на Android 16: установка, холодный запуск, ZIP-структура и v1/v2-подписи корректны.
|
- финальный debug APK проверен на Android 16: установка, холодный запуск, ZIP-структура и v1/v2-подписи корректны.
|
||||||
|
|
||||||
Главный текущий риск: автоматическая проходимость доказана, но ручная понятность и баланс этапа 2 ещё не подтверждены. Отдельно остаются гипотезы об альтернативных решениях уровня 2 и обходе frozen corpse на уровне 10.
|
Автоматическая проходимость, устойчивость обязательных объектов и чистый restart доказаны. Внешняя понятность уровней не может быть сертифицирована самим automation-runner: две ручные сессии на уровень остаются отдельным внешним playtest-gate и не считаются выполненными этим отчётом.
|
||||||
|
|
||||||
Performance-оговорка: критерий этапа 1 «полный прогон до 5 минут» пока не выполнен — финальный зелёный matrix занимает `5:59`. Сокращать количество трёхкратных прохождений или скрывать нестабильность retries запрещено; требуется отдельная оптимизация тестового runner.
|
Performance-оговорка: исторический критерий этапа 1 «полный прогон до 5 минут» не выполнен на текущем throttled AVD. После расширения набора до 16 методов exact final matrix занимает `9:32.496`. Сокращать трёхкратные прохождения или скрывать нестабильность retries запрещено; оптимизация runner остаётся отдельной QA-задачей и не меняет доказанную проходимость.
|
||||||
|
|
||||||
## 2. Принципы разработки
|
## 2. Принципы разработки
|
||||||
|
|
||||||
@@ -173,15 +175,8 @@ Performance-оговорка: критерий этапа 1 «полный пр
|
|||||||
8. холодный запуск и проверка crash-буфера;
|
8. холодный запуск и проверка crash-буфера;
|
||||||
9. ручной smoke-тест уровня 1 и одного сложного комбинированного уровня.
|
9. ручной smoke-тест уровня 1 и одного сложного комбинированного уровня.
|
||||||
|
|
||||||
## 5. Текущая итерация
|
## 5. Завершение этапа 2 и следующая итерация
|
||||||
|
|
||||||
Этап 1 завершён автоматическим доказательством intended route уровней 1–10. Текущая итерация ограничивается этапом 2:
|
Инженерный scope этапа 2 закрыт: уровни 1–10 проходят intended route по три раза, уровень 8 имеет restart-free recovery, уровень 10 использует все три тела и сохраняет тело на lift-плите, а restart возвращает чистое состояние. Передаваемые результаты — обновлённый проект, проверенный APK и [`STAGE2_QA_REPORT.md`](STAGE2_QA_REPORT.md).
|
||||||
|
|
||||||
- выполнить полный integrated `10/10 passed` перед каждым APK;
|
Следующая реализационная итерация — этап 3 (управление, интерфейс и доступность). Параллельно внешний QA должен выполнить `goon-game-human-playtest`: по две ручные сессии каждого уровня без подсказок разработчика. До получения этого протокола milestone Vertical Slice нельзя называть принятым внешними тестировщиками, хотя разработку этапа 3 можно начинать.
|
||||||
- провести по две ручные сессии каждого уровня и зафиксировать попытки, restart и непонятные места;
|
|
||||||
- скорректировать допуски прыжков и подсказки только по наблюдаемым проблемам;
|
|
||||||
- проверить чистый restart из ключевых промежуточных состояний;
|
|
||||||
- закрыть или документировать гипотезы альтернативных решений уровней 2 и 10;
|
|
||||||
- не добавлять уровень 11 и новые механики до завершения критериев этапа 2.
|
|
||||||
|
|
||||||
Результатом итерации должны стать обновлённый исходный проект, проверенный APK, integrated bot report и протокол ручных игровых сессий.
|
|
||||||
|
|||||||
@@ -199,15 +199,15 @@ Door закрывает путь к goal
|
|||||||
|
|
||||||
**Статус:** intended route и восстановление после истечения таймера доказаны production-input ботом, по три раза подряд.
|
**Статус:** intended route и восстановление после истечения таймера доказаны production-input ботом, по три раза подряд.
|
||||||
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
||||||
**Источник:** `LevelCatalog.level8()`; основной `TIMED=(560,810,90,70)`, `signalId="clock"`, duration `6.0`; recovery relay `TIMED=(1600,650,90,70)`, duration `3.5`; moving platform `(990,770,240,34)`, travel `(380,0)`, period `4.0`, `controlId="clock"`; `PIT=(970,880,540,200)`; дверь `(1770,560,82,320)` требует `"clock"`; `goal=(2220,600,86,110)`.
|
**Источник:** `LevelCatalog.level8()`; основной `TIMED=(560,810,90,70)`, `signalId="clock"`, duration `8.0`; recovery relay `TIMED=(1600,650,90,70)`, duration `3.5`; moving platform `(910,770,320,34)`, travel `(380,0)`, period `4.0`, `controlId="clock"`; `PIT=(1040,880,470,200)`; дверь `(1770,560,82,320)` требует `"clock"`; `goal=(2220,600,86,110)`.
|
||||||
|
|
||||||
### Solution graph 8
|
### Solution graph 8
|
||||||
|
|
||||||
```text
|
```text
|
||||||
PIT разрывает floor, а Door закрывает дальнейший путь
|
PIT разрывает floor, а Door закрывает дальнейший путь
|
||||||
→ войти в TIMED и запустить signal "clock" на 6.0 s
|
→ войти в TIMED и запустить signal "clock" на 8.0 s
|
||||||
→ "clock" одновременно запускает horizontal moving platform и открывает Door
|
→ "clock" одновременно запускает horizontal moving platform и открывает Door
|
||||||
→ без задержки добежать до края floor x=970
|
→ без задержки добежать до края floor x=1040
|
||||||
→ прыгнуть на moving platform и пересечь PIT
|
→ прыгнуть на moving platform и пересечь PIT
|
||||||
→ сойти на правый floor x≥1510
|
→ сойти на правый floor x≥1510
|
||||||
→ пройти Door x=1770..1852 до истечения "clock"
|
→ пройти Door x=1770..1852 до истечения "clock"
|
||||||
@@ -222,9 +222,9 @@ PIT разрывает floor, а Door закрывает дальнейший п
|
|||||||
→ пройти Door и достичь goal без смерти/restart
|
→ пройти Door и достичь goal без смерти/restart
|
||||||
```
|
```
|
||||||
|
|
||||||
**Механика и сигналы:** фронт контакта основного `TIMED` → `timedSignals["clock"]=6.0` → движение платформы по `travelX=380` и открытие Door; высокий relay выставляет тот же сигнал на `3.5 s`. Обычный бег проходит под relay, поэтому original route не активирует восстановление автоматически. Строгий grounded-checkpoint выявил нестабильное закрытие Door на `5.5 s`; добавленные `0.5 s` дают запас на проход двери. Android 16 при задержках между решениями бота выявил отдельное падение с узкого стартового края: floor продлён на `70` px, а начало PIT сдвинуто с `900` на `970` без изменения его правого края `1510`, платформы, таймеров или recovery relay.
|
**Механика и сигналы:** фронт контакта основного `TIMED` → `timedSignals["clock"]=8.0` → движение платформы по `travelX=380` и открытие Door; высокий relay выставляет тот же сигнал на `3.5 s`. Обычный бег проходит под relay, поэтому original route не активирует восстановление автоматически. Android 16 при задержках между решениями выявил узкое окно посадки: launch floor продлён до `x=1040`, а платформа расширена влево до `x=910` при сохранении прежнего правого края `x=1230`, дальнего края PIT `x=1510`, travel, period и recovery relay.
|
||||||
|
|
||||||
**Bot checkpoints:** основной timer активен; игрок двумя прыжками пересекает PIT через moving platform; перед движением к relay явно подтверждены far floor `playerX≥1510`, `playerY≥779` и всё ещё открытая Door. Intended route проходит Door на первом таймере. Recovery route ждёт `allDoorsOpen=false` справа от PIT, прыжком получает `allDoorsOpen=true`, проходит Door и завершает уровень. Оба маршрута финально дают `completed=true`, `deaths=0`; каждый выполнен три раза подряд на Android 16. Дополнительный intended run с `96 ms` задержки на каждое решение также завершён без смерти. Попытка расширить платформу была отклонена тестом: она могла унести ожидающего игрока с дальнего floor обратно над PIT после закрытия двери.
|
**Bot checkpoints:** основной timer активен; первый прыжок подтверждает `playerOnMovingPlatform=true`, второй доставляет игрока на дальний floor. Перед движением к relay явно подтверждены `playerX≥1510`, `playerY≥779` и всё ещё открытая Door. Intended route проходит Door на первом таймере. Recovery route ждёт `allDoorsOpen=false` справа от PIT, прыжком получает `allDoorsOpen=true`, проходит Door и завершает уровень. Оба маршрута финально дают `completed=true`, `deaths=0`; каждый выполнен три раза подряд на Android 16. Дополнительный intended run с `96 ms` задержки на каждое решение также завершён без смерти. Дальний край платформы намеренно не расширен, чтобы она не могла унести ожидающего игрока обратно над PIT.
|
||||||
|
|
||||||
**Softlock/неопределённость:** прежний blocker устранён: после истечения `"clock"` справа от PIT высокий relay гарантирует restart-free повторное открытие двери. Relay не помогает слева от PIT и не отменяет timed-platform crossing. Оба пути проверены без прямой мутации состояния, teleport, auto-complete или restart.
|
**Softlock/неопределённость:** прежний blocker устранён: после истечения `"clock"` справа от PIT высокий relay гарантирует restart-free повторное открытие двери. Relay не помогает слева от PIT и не отменяет timed-platform crossing. Оба пути проверены без прямой мутации состояния, teleport, auto-complete или restart.
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ Final Door использует AND из "weight" и "power"
|
|||||||
|
|
||||||
**Статус:** mastery-маршрут доказан production-input ботом: 3/3 последовательных прохождения и ограниченный стресс финального перехода `96 ms` без ретраев.
|
**Статус:** mastery-маршрут доказан production-input ботом: 3/3 последовательных прохождения и ограниченный стресс финального перехода `96 ms` без ретраев.
|
||||||
**Ожидаемые и доказанные смерти:** `3` (`targetDeaths=3`).
|
**Ожидаемые и доказанные смерти:** `3` (`targetDeaths=3`).
|
||||||
**Источник:** `LevelCatalog.level10()`; `SAW=(520,760,96,96)`, impulse `(700,-240)`; `PRESSURE=(860,860,160,20)`, `signalId="lift"`; `FREEZER=(1110,750,100,130)`; `ELECTRIC=(1510,780,110,100)`, `relayId="power"`; moving platform `(1840,790,250,34)`, travel `(0,-410)`, period `5.0`, `controlId="lift"`; стартовый floor заканчивается на `x=1940`; `PIT=(1940,880,200,200)`; дверь `(2310,310,82,570)` требует `"power"`; верхний уступ `(2420,440,680)`; `goal=(2880,330,86,110)`.
|
**Источник:** `LevelCatalog.level10()`; `SAW=(520,760,96,96)`, impulse `(700,-240)`; `PRESSURE=(860,860,160,20)`, `signalId="lift"`; `FREEZER=(1110,776,100,104)`; `ELECTRIC=(1510,780,110,100)`, `relayId="power"`; moving platform `(1800,790,290,34)`, travel `(0,-410)`, period `10.0`, `controlId="lift"`; стартовый floor заканчивается на `x=1940`; `PIT=(1940,880,200,200)`; дверь `(2310,310,82,570)` требует `"power"`; верхний уступ `(2420,440,680)`; `goal=(2880,330,86,110)`.
|
||||||
|
|
||||||
### Solution graph 10
|
### Solution graph 10
|
||||||
|
|
||||||
@@ -267,23 +267,23 @@ Final Door использует AND из "weight" и "power"
|
|||||||
Goal расположен на верхнем уступе за PIT и закрытой высокой Door
|
Goal расположен на верхнем уступе за PIT и закрытой высокой Door
|
||||||
→ первый доброволец входит в SAW
|
→ первый доброволец входит в SAW
|
||||||
→ impulse (700,-240) доставляет тело на PRESSURE x=860..1020
|
→ impulse (700,-240) доставляет тело на PRESSURE x=860..1020
|
||||||
→ тело удерживает signal "lift" и запускает вертикальную moving platform
|
→ тело оседает и фиксируется на PRESSURE, удерживает signal "lift" и запускает vertical moving platform
|
||||||
→ следующий доброволец погибает в FREEZER
|
→ следующий доброволец погибает в FREEZER
|
||||||
→ frozen corpse становится стабильной ступенью для повторного прохода этой зоны
|
→ frozen corpse становится стабильной ступенью для повторного прохода этой зоны
|
||||||
→ следующий доброволец погибает в ELECTRIC
|
→ следующий доброволец погибает в ELECTRIC
|
||||||
→ conductive corpse удерживает relay signal "power" и открывает Door
|
→ conductive corpse удерживает relay signal "power" и открывает Door
|
||||||
→ четвёртый доброволец respawn-ится и пересекает сохранённые тела/опасности
|
→ четвёртый доброволец respawn-ится и пересекает сохранённые тела/опасности
|
||||||
→ садится на активную platform у x=1840..2090
|
→ садится на активную platform у x=1800..2090
|
||||||
→ поднимается над PIT и в верхней фазе прыгает вправо через открытую Door
|
→ поднимается над PIT и в верхней фазе прыгает вправо через открытую Door
|
||||||
→ приземляется на ledge x=2420..3100
|
→ приземляется на ledge x=2420..3100
|
||||||
→ достигает goal и получает COMPLETE с тремя смертями
|
→ достигает goal и получает COMPLETE с тремя смертями
|
||||||
```
|
```
|
||||||
|
|
||||||
**Механика и сигналы:** launched saw corpse → `"lift"` → vertical platform; frozen corpse → твёрдая ступень; conductive corpse → `"power"` → Door open; platform и Door используют независимые постоянные сигналы от сохранённых тел.
|
**Механика и сигналы:** launched saw corpse при контакте с `PRESSURE` обнуляет скорость и закрепляется внутри плиты → `"lift"` → vertical platform; frozen corpse → твёрдая ступень; conductive corpse → `"power"` → Door open; platform и Door используют независимые постоянные сигналы от сохранённых тел.
|
||||||
|
|
||||||
**Bot checkpoints:** после SAW `deaths=1`, `"lift"=true`, platform `y<790`; после FREEZER `deaths=2` и наличие frozen corpse; после ELECTRIC `deaths=3`, `"power"=true`, `allDoorsOpen=true`; перед посадкой первая moving platform должна вернуться к `y≥780`, затем snapshot подтверждает `playerOnMovingPlatform=true`; в верхней фазе `playerY≤280`; после прыжка `playerX≥2420`, `playerY≤340`; финально `completed=true`.
|
**Bot checkpoints:** после SAW `deaths=1`, `"lift"=true`, platform `y<790`; после FREEZER `deaths=2` и наличие frozen corpse; после ELECTRIC `deaths=3`, `"power"=true`, `allDoorsOpen=true`; перед посадкой первая moving platform должна вернуться к `y≥780`, затем snapshot подтверждает `playerOnMovingPlatform=true`; в верхней фазе `playerY≤280`; после прыжка `playerX≥2420`, `playerY≤340`; финально `completed=true`.
|
||||||
|
|
||||||
**Softlock/неопределённость:** интегрированный прогон воспроизвёл падение при посадке с исходным 40 px перекрытием floor/lift. Перекрытие увеличено до 100 px, при этом сохранены 200 px `PIT` и обязательный вертикальный подъём. Доставка saw-тела, три сохранённых тела, посадка, подъём и lift→ledge прыжок подтверждены тремя запусками и стресс-сценарием. Обязательность frozen corpse всё ещё не доказана отрицательным тестом: возможный двухсмертный sequence break остаётся follow-up.
|
**Softlock/неопределённость:** исправлены два независимых blocker-состояния: saw-тело больше не соскальзывает с плиты после доставки, а перекрытие floor/lift увеличено до 140 px при сохранении 200 px `PIT` и обязательного вертикального подъёма. Период lift увеличен до `10.0 s`, чтобы нижняя фаза была читаемой при задержанном вводе. Бот теперь явно приземляется на frozen corpse и прыгает с него; доставка saw-тела, три сохранённых тела, посадка, подъём и lift→ledge прыжок подтверждены тремя запусками и стресс-сценарием. Возможная альтернативная двухсмертная трасса не считается blocker, пока intended three-body route остаётся стабильным.
|
||||||
|
|
||||||
## Проверки, следующие из графов
|
## Проверки, следующие из графов
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Stage 2 QA report
|
||||||
|
|
||||||
|
Дата: 18 августа 2026 года. Сборка: `0.4.0-stage2-complete-debug` (`versionCode=5`). Целевая среда приёмки: Android 16, `medium_phone`, `arm64`.
|
||||||
|
|
||||||
|
## Исправленный blocker
|
||||||
|
|
||||||
|
На уровне 10 тело, доставленное пилой на плиту `lift`, продолжало скользить и могло отключить платформу. До исправления focused device-test зафиксировал изменение `firstCorpseX` с `791.4791` до `866.5033` за 750 ms. Теперь заземлённое тело при контакте с `PRESSURE` полностью размещается внутри плиты, его скорости обнуляются, и оно закрепляется. Тест дополнительно ждёт 6 секунд и подтверждает активный lift, ровно одну смерть и одно сохранённое тело.
|
||||||
|
|
||||||
|
## Автоматическая приёмка
|
||||||
|
|
||||||
|
- `testDebugUnitTest`: 17 JVM tests, GREEN;
|
||||||
|
- `lintDebug`: GREEN;
|
||||||
|
- `assembleDebug` и `assembleDebugAndroidTest`: GREEN;
|
||||||
|
- полный unfiltered Android matrix на exact final APK: `OK (16 tests)`, `572.496 s`;
|
||||||
|
- внутри matrix: 36 игровых выполнений, lifecycle и restart;
|
||||||
|
- intended route уровней 1–10 выполняется по три раза без retry masking;
|
||||||
|
- уровень 8: дополнительный stressed run и три restart-free recovery run;
|
||||||
|
- уровень 10: отдельный тест стабильности тела, stressed traversal и три mastery run ровно с тремя телами;
|
||||||
|
- restart уровня 10 очищает тела, сигналы, таймер и удерживаемый ввод.
|
||||||
|
|
||||||
|
## APK verification
|
||||||
|
|
||||||
|
- package: `com.projectsacrifice.game.debug`;
|
||||||
|
- version: `versionCode=5`, `versionName=0.4.0-stage2-complete-debug`;
|
||||||
|
- SDK: `minSdk=23`, `targetSdk=34`;
|
||||||
|
- ZIP integrity: `unzip -t` — no errors;
|
||||||
|
- signature: APK Signature Scheme v1/v2 — verified, one Android Debug signer;
|
||||||
|
- clean install: successful;
|
||||||
|
- cold launch: `Status: ok`, `LaunchState: COLD`, `TotalTime: 972 ms`;
|
||||||
|
- Android crash buffer after launch: empty;
|
||||||
|
- SHA-256: `1bc14020915ebc3b9be4760e0ae83584d8bf2768bbf6e692de24562ec7256bcd`.
|
||||||
|
|
||||||
|
## Граница подтверждения
|
||||||
|
|
||||||
|
Отчёт доказывает инженерную проходимость и стабильность на указанном AVD. Он не подменяет внешний тест понятности: две ручные сессии на каждый уровень без объяснений разработчика остаются задачей `goon-game-human-playtest`. Историческая цель полного matrix менее 5 минут также не достигнута на throttled AVD; exact final набор из 16 методов занимает `9:32.496`.
|
||||||
Reference in New Issue
Block a user