Обновить Stage 2 регрессию и roadmap
This commit is contained in:
@@ -43,14 +43,14 @@ Requirements: JDK 17 and Android SDK Platform/Build Tools 34.
|
|||||||
./gradlew testDebugUnitTest assembleDebug
|
./gradlew testDebugUnitTest assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
Level 5 has a device-level acceptance bot. It drives the production `GameView`
|
All ten levels have device-level acceptance bots. They drive the production
|
||||||
through the same movement and jump inputs as the touch controls, sacrifices one
|
`GameView` through the same movement and jump inputs as the touch controls and
|
||||||
volunteer to the saw, verifies the retained body and pressure-plate door, then
|
run each intended solution three times. Level 8 also has a dedicated recovery
|
||||||
reaches the goal. With an emulator or phone connected, run it with:
|
scenario for an expired timer on the far side of the pit. With an emulator or
|
||||||
|
phone connected, run the complete matrix with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./gradlew connectedDebugAndroidTest \
|
./gradlew connectedDebugAndroidTest
|
||||||
-Pandroid.testInstrumentationRunnerArguments.class=com.projectsacrifice.game.LevelFivePlaythroughTest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The debug APK is written to `app/build/outputs/apk/debug/app-debug.apk`. It is signed with Android's standard debug key and can be installed with:
|
The debug APK is written to `app/build/outputs/apk/debug/app-debug.apk`. It is signed with Android's standard debug key and can be installed with:
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ android {
|
|||||||
applicationId 'com.projectsacrifice.game'
|
applicationId 'com.projectsacrifice.game'
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 3
|
versionCode 4
|
||||||
versionName '0.2.1-level5-proof'
|
versionName '0.3.0-stage2-playability'
|
||||||
|
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import org.junit.runner.RunWith;
|
|||||||
/** Acceptance test: a bot must finish the same level implementation shipped in the APK. */
|
/** Acceptance test: a bot must finish the same level implementation shipped in the APK. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class LevelFivePlaythroughTest {
|
public final class LevelFivePlaythroughTest {
|
||||||
|
private static final int CONSECUTIVE_RUNS = 3;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBotCompletesLevelFive() {
|
public void testBotCompletesLevelFive() {
|
||||||
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
|
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
|
||||||
@@ -19,15 +21,20 @@ public final class LevelFivePlaythroughTest {
|
|||||||
scenario.onActivity(activity -> view[0] = activity.gameViewForAutomation());
|
scenario.onActivity(activity -> view[0] = activity.gameViewForAutomation());
|
||||||
|
|
||||||
LevelPlaytestBot bot = new LevelPlaytestBot();
|
LevelPlaytestBot bot = new LevelPlaytestBot();
|
||||||
LevelPlaytestBot.Result result = bot.proveLevelFive(
|
AndroidDriver driver = new AndroidDriver(
|
||||||
new AndroidDriver(InstrumentationRegistry.getInstrumentation(), view[0]),
|
InstrumentationRegistry.getInstrumentation(), view[0]);
|
||||||
25_000L
|
for (int run = 1; run <= CONSECUTIVE_RUNS; run++) {
|
||||||
);
|
LevelPlaytestBot.Result result = bot.proveLevelFive(driver, 25_000L);
|
||||||
|
|
||||||
org.junit.Assert.assertTrue(result.diagnostic(), result.completed());
|
org.junit.Assert.assertTrue("run=" + run + "; " + result.diagnostic(),
|
||||||
org.junit.Assert.assertTrue("The solution must use at least one volunteer", result.deaths() >= 1);
|
result.completed());
|
||||||
org.junit.Assert.assertTrue("The delivered corpse must remain in the puzzle", result.corpseCount() >= 1);
|
org.junit.Assert.assertEquals("run=" + run + "; exactly one volunteer is required",
|
||||||
org.junit.Assert.assertTrue("The pressure-plate door must open", result.doorOpened());
|
1, result.deaths());
|
||||||
|
org.junit.Assert.assertEquals("run=" + run + "; delivered corpse must remain",
|
||||||
|
1, result.corpseCount());
|
||||||
|
org.junit.Assert.assertTrue("run=" + run + "; pressure-plate door must open",
|
||||||
|
result.doorOpened());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-10
@@ -13,10 +13,12 @@
|
|||||||
- смерть не сбрасывает состояние уровня, тела остаются физическими объектами;
|
- смерть не сбрасывает состояние уровня, тела остаются физическими объектами;
|
||||||
- реализованы шипы, пила, нажимные и временные кнопки, двери, движущиеся платформы, заморозка и электричество;
|
- реализованы шипы, пила, нажимные и временные кнопки, двери, движущиеся платформы, заморозка и электричество;
|
||||||
- сохраняются прогресс, лучшее время и минимальное число жертв;
|
- сохраняются прогресс, лучшее время и минимальное число жертв;
|
||||||
- уровень 5 защищён Android acceptance-тестом: бот проходит реальную игровую сцену примерно за 8,2 секунды с одной смертью и одним оставшимся телом;
|
- intended route всех уровней 1–10 защищён Android acceptance-ботами: каждый маршрут выполняется по три раза через production-ввод без прямого перемещения персонажа;
|
||||||
|
- blocker-softlock уровня 8 после истечения таймера устранён recovery relay и покрыт отдельным трёхкратным сценарием;
|
||||||
|
- исправлено перекрытие `FREEZER` уровня 6, которое повторно убивало игрока на frozen stepping stone;
|
||||||
- финальный debug APK проверен на Android 16: установка, холодный запуск, ZIP-структура и v1/v2-подписи корректны.
|
- финальный debug APK проверен на Android 16: установка, холодный запуск, ZIP-структура и v1/v2-подписи корректны.
|
||||||
|
|
||||||
Главный текущий риск: автоматическая проходимость доказана только для уровня 5. Остальные уровни могут содержать тупики, нестабильную физику или слишком точные действия.
|
Главный текущий риск: автоматическая проходимость доказана, но ручная понятность и баланс этапа 2 ещё не подтверждены. Отдельно остаются гипотезы об альтернативных решениях уровня 2 и обходе frozen corpse на уровне 10.
|
||||||
|
|
||||||
## 2. Принципы разработки
|
## 2. Принципы разработки
|
||||||
|
|
||||||
@@ -168,14 +170,15 @@
|
|||||||
8. холодный запуск и проверка crash-буфера;
|
8. холодный запуск и проверка crash-буфера;
|
||||||
9. ручной smoke-тест уровня 1 и одного сложного комбинированного уровня.
|
9. ручной smoke-тест уровня 1 и одного сложного комбинированного уровня.
|
||||||
|
|
||||||
## 5. Ближайшая итерация
|
## 5. Текущая итерация
|
||||||
|
|
||||||
Следующая итерация ограничивается этапом 1:
|
Этап 1 завершён автоматическим доказательством intended route уровней 1–10. Текущая итерация ограничивается этапом 2:
|
||||||
|
|
||||||
- обобщить существующего бота уровня 5;
|
- выполнить полный integrated `10/10 passed` перед каждым APK;
|
||||||
- добавить ботов уровней 1–4;
|
- провести по две ручные сессии каждого уровня и зафиксировать попытки, restart и непонятные места;
|
||||||
- затем добавить ботов уровней 6–10;
|
- скорректировать допуски прыжков и подсказки только по наблюдаемым проблемам;
|
||||||
- зафиксировать единый отчёт `10/10 passed`;
|
- проверить чистый restart из ключевых промежуточных состояний;
|
||||||
- не добавлять уровень 11 и новые механики до выполнения этого критерия.
|
- закрыть или документировать гипотезы альтернативных решений уровней 2 и 10;
|
||||||
|
- не добавлять уровень 11 и новые механики до завершения критериев этапа 2.
|
||||||
|
|
||||||
Результатом итерации должны стать обновлённый исходный проект, проверенный APK и отчёт с длительностью и результатом каждого сценария.
|
Результатом итерации должны стать обновлённый исходный проект, проверенный APK, integrated bot report и протокол ручных игровых сессий.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Графы решений уровней 1–10
|
# Графы решений уровней 1–10
|
||||||
|
|
||||||
Документ фиксирует **предполагаемый авторский маршрут** каждого уровня текущего vertical slice. Он получен из `LevelCatalog`, моделей `LevelDefinition` и фактической логики `GameView`, а не из ручного прохождения. `targetDeaths` ниже — целевое число жертв для результата уровня, но оно не доказывает минимальность решения.
|
Документ фиксирует **авторский маршрут** каждого уровня текущего vertical slice. Он получен из `LevelCatalog`, моделей `LevelDefinition` и фактической логики `GameView`, затем проверен device-level ботами через production-команды движения и прыжка. `targetDeaths` ниже — целевое число жертв для результата уровня, но оно не доказывает минимальность решения.
|
||||||
|
|
||||||
На 18 августа 2026 года автоматически доказан только уровень 5: `LevelFivePlaythroughTest` управляет production `GameView` через те же флаги движения и прыжка, что и touch-контролы. Все остальные графы имеют статус «предполагаемый, не доказан ботом» до появления воспроизводимого `connectedDebugAndroidTest`.
|
На 18 августа 2026 года intended route всех уровней 1–10 автоматически выполнен по три раза подряд без retry masking. `EarlyLevelPlaythroughTest`, `LevelFivePlaythroughTest`, `LateLevelPlaythroughTest` и `LevelEightPlaythroughTest` управляют production `GameView` через те же флаги, что и touch-контролы. Для уровня 8 отдельно проверено восстановление после истечения таймера на дальней стороне `PIT`.
|
||||||
|
|
||||||
## Семантика, общая для графов
|
## Семантика, общая для графов
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
## Уровень 1 — `The Volunteer`
|
## Уровень 1 — `The Volunteer`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения без смертей.
|
||||||
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
||||||
**Источник:** `LevelCatalog.level1()`; уступы `(640,760,230)` и `(1060,690,250)`; `goal=(1690,770,86,110)`.
|
**Источник:** `LevelCatalog.level1()`; уступы `(640,760,230)` и `(1060,690,250)`; `goal=(1690,770,86,110)`.
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ Goal расположен справа от двух перекрывающих
|
|||||||
|
|
||||||
**Bot checkpoints:** `playerX>640` при `playerY≤660` → `playerX>1060` при `playerY≤590` → `playerX>1310` → `completed=true`, `deaths=0`.
|
**Bot checkpoints:** `playerX>640` при `playerY≤660` → `playerX>1060` при `playerY≤590` → `playerX>1310` → `completed=true`, `deaths=0`.
|
||||||
|
|
||||||
**Softlock/неопределённость:** blocker-softlock по данным модели не ожидается; точные окна двух прыжков и возможность альтернативного прохода под/мимо уступов автоматически не проверены.
|
**Softlock/неопределённость:** intended route и окна двух прыжков воспроизводимо подтверждены. Альтернативный проход под/мимо уступов отдельно не проверялся.
|
||||||
|
|
||||||
## Уровень 2 — `A Useful End`
|
## Уровень 2 — `A Useful End`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения с тремя телами.
|
||||||
**Ожидаемые смерти:** `3` (`targetDeaths=3`).
|
**Ожидаемые смерти:** `3` (`targetDeaths=3`).
|
||||||
**Источник:** `LevelCatalog.level2()`; `SPIKES=(610,850,260,30)`; статичный уступ `(1180,740,240)`; `goal=(1830,770,86,110)`.
|
**Источник:** `LevelCatalog.level2()`; `SPIKES=(610,850,260,30)`; статичный уступ `(1180,740,240)`; `goal=(1830,770,86,110)`.
|
||||||
|
|
||||||
@@ -61,11 +61,11 @@ Spike lane x=610..870 перекрывает наземный маршрут
|
|||||||
|
|
||||||
**Bot checkpoints:** после каждой предусмотренной жертвы `deaths/corpseCount=1/1`, `2/2`, `3/3`; затем живой игрок `playerX>870`; финально `completed=true`, `deaths=3`.
|
**Bot checkpoints:** после каждой предусмотренной жертвы `deaths/corpseCount=1/1`, `2/2`, `3/3`; затем живой игрок `playerX>870`; финально `completed=true`, `deaths=3`.
|
||||||
|
|
||||||
**Softlock/неопределённость:** тела становятся горизонтальными коллайдерами и требуют прыжка уже после первой смерти. При текущих `RUN_SPEED`/`JUMP_SPEED` ширина lane может допускать перепрыгивание с меньшим числом смертей; это возможный sequence break, а не подтверждённый маршрут. Нужны бот-проверка intended route и отдельный negative/alternative-solution тест.
|
**Softlock/неопределённость:** intended route с тремя телами воспроизводимо подтверждён. Отдельная попытка прямого zero-death прохода не подтвердила sequence break, но не является исчерпывающим negative-тестом; минимальность трёх смертей остаётся открытым follow-up.
|
||||||
|
|
||||||
## Уровень 3 — `Dead Weight`
|
## Уровень 3 — `Dead Weight`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения с одной жертвой.
|
||||||
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
|
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
|
||||||
**Источник:** `LevelCatalog.level3()`; `SAW=(565,760,92,92)`, impulse `(0,-150)`; `PRESSURE=(540,860,150,20)`, `signalId="gate"`; дверь `(1110,570,78,310)` требует `"gate"`.
|
**Источник:** `LevelCatalog.level3()`; `SAW=(565,760,92,92)`, impulse `(0,-150)`; `PRESSURE=(540,860,150,20)`, `signalId="gate"`; дверь `(1110,570,78,310)` требует `"gate"`.
|
||||||
|
|
||||||
@@ -87,11 +87,11 @@ Door закрывает путь к goal
|
|||||||
|
|
||||||
**Bot checkpoints:** `deaths=1`, `corpseCount=1`; после respawn `playerAlive=true`; требуется расширить snapshot состоянием `"gate"` либо использовать `allDoorsOpen=true`; затем `playerX>1188` и `completed=true`.
|
**Bot checkpoints:** `deaths=1`, `corpseCount=1`; после respawn `playerAlive=true`; требуется расширить snapshot состоянием `"gate"` либо использовать `allDoorsOpen=true`; затем `playerX>1188` и `completed=true`.
|
||||||
|
|
||||||
**Softlock/неопределённость:** успех зависит от фактической точки первого пересечения игрока с `SAW`, потому что тело сохраняет `player.x`; достаточное перекрытие с плитой ботом ещё не доказано. Если тело не удержит плиту, продолжение невозможно без дополнительной жертвы или restart.
|
**Softlock/неопределённость:** доставка тела на плиту, удержание двери и последующий проход подтверждены в трёх последовательных запусках. Альтернативные точки контакта с `SAW` отдельно не перебирались.
|
||||||
|
|
||||||
## Уровень 4 — `The Long Lift`
|
## Уровень 4 — `The Long Lift`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения без смертей.
|
||||||
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
||||||
**Источник:** `LevelCatalog.level4()`; `TOGGLE=(520,820,72,60)`, `signalId="lift"`; moving platform `(1040,790,260,34)`, travel `(0,-390)`, period `5.2`, `controlId="lift"`; `PIT=(1500,880,320,200)`; верхний уступ `(1660,500,540)`; `goal=(1960,390,86,110)`.
|
**Источник:** `LevelCatalog.level4()`; `TOGGLE=(520,820,72,60)`, `signalId="lift"`; moving platform `(1040,790,260,34)`, travel `(0,-390)`, period `5.2`, `controlId="lift"`; `PIT=(1500,880,320,200)`; верхний уступ `(1660,500,540)`; `goal=(1960,390,86,110)`.
|
||||||
|
|
||||||
@@ -112,12 +112,12 @@ Goal находится на верхней галерее за PIT
|
|||||||
|
|
||||||
**Bot checkpoints:** `signal "lift"=true`; координата платформы уменьшается от `y=790`; игрок получает `supportIndex` движущейся платформы; затем `playerX≥1660`, `playerY≤400`; финально `completed=true`, `deaths=0`. Для этого snapshot нужно расширить сигналами, `supportIndex` и состоянием платформы.
|
**Bot checkpoints:** `signal "lift"=true`; координата платформы уменьшается от `y=790`; игрок получает `supportIndex` движущейся платформы; затем `playerX≥1660`, `playerY≤400`; финально `completed=true`, `deaths=0`. Для этого snapshot нужно расширить сигналами, `supportIndex` и состоянием платформы.
|
||||||
|
|
||||||
**Softlock/неопределённость:** повторный контакт с `TOGGLE` выключит сигнал и заморозит платформу в текущей фазе. Прыжок от правого края платформы к началу галереи имеет небольшой геометрический запас и не подтверждён ботом; это риск точного тайминга/непроходимости.
|
**Softlock/неопределённость:** lift→gallery прыжок подтверждён в трёх последовательных запусках. Повторный контакт с `TOGGLE` по-прежнему выключает сигнал и может заморозить платформу; это предусмотренное состояние, доступное для restart.
|
||||||
|
|
||||||
## Уровень 5 — `Express Delivery`
|
## Уровень 5 — `Express Delivery`
|
||||||
|
|
||||||
**Статус:** автоматически доказан `LevelFivePlaythroughTest` на production `GameView`.
|
**Статус:** доказан `LevelFivePlaythroughTest`, 3/3 последовательных прохождения.
|
||||||
**Ожидаемые смерти:** `1` (`targetDeaths=1`); acceptance-тест требует минимум одну.
|
**Ожидаемые смерти:** `1` (`targetDeaths=1`); acceptance-тест требует ровно одну.
|
||||||
**Источник:** `LevelCatalog.level5()`; `SAW=(570,760,96,96)`, impulse `(500,-700)`; уступ `(850,800,330)`; `PRESSURE=(930,780,160,20)`, `signalId="gate"`; дверь `(1390,560,82,320)` требует `"gate"`; `goal=(2080,770,86,110)`.
|
**Источник:** `LevelCatalog.level5()`; `SAW=(570,760,96,96)`, impulse `(500,-700)`; уступ `(850,800,330)`; `PRESSURE=(930,780,160,20)`, `signalId="gate"`; дверь `(1390,560,82,320)` требует `"gate"`; `goal=(2080,770,86,110)`.
|
||||||
|
|
||||||
### Solution graph 5
|
### Solution graph 5
|
||||||
@@ -140,11 +140,11 @@ Door закрывает путь к goal
|
|||||||
|
|
||||||
**Bot checkpoints:** реализованы в `LevelPlaytestBot.proveLevelFive`: первая смерть; `corpseCount≥1`; `allDoorsOpen=true`; прыжок после `playerX≥390`, дополнительный прыжок после `playerX≥750`; затем `completed=true`. Тест проверяет `completed`, смерть, сохранённое тело и факт открытия двери.
|
**Bot checkpoints:** реализованы в `LevelPlaytestBot.proveLevelFive`: первая смерть; `corpseCount≥1`; `allDoorsOpen=true`; прыжок после `playerX≥390`, дополнительный прыжок после `playerX≥750`; затем `completed=true`. Тест проверяет `completed`, смерть, сохранённое тело и факт открытия двери.
|
||||||
|
|
||||||
**Softlock/неопределённость:** основной маршрут воспроизводимо подтверждён одним device-level acceptance-тестом, но пока не выполняется требование roadmap о трёх последовательных прогонах. Альтернативные решения, restart и физическая стабильность на нескольких frame rates этим тестом не покрыты.
|
**Softlock/неопределённость:** основной маршрут воспроизводимо подтверждён тремя последовательными запусками. Альтернативные решения и физическая стабильность на нескольких frame rates этим тестом не покрыты.
|
||||||
|
|
||||||
## Уровень 6 — `Cold Comfort`
|
## Уровень 6 — `Cold Comfort`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения с frozen stepping stone.
|
||||||
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
|
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
|
||||||
**Источник:** `LevelCatalog.level6()`; `FREEZER=(690,776,100,104)`; floor заканчивается на `x=1120`; уступы `(810,590,520)` и `(1430,610,570)`; `goal=(1880,500,86,110)`.
|
**Источник:** `LevelCatalog.level6()`; `FREEZER=(690,776,100,104)`; floor заканчивается на `x=1120`; уступы `(810,590,520)` и `(1430,610,570)`; `goal=(1880,500,86,110)`.
|
||||||
|
|
||||||
@@ -166,11 +166,11 @@ Door закрывает путь к goal
|
|||||||
|
|
||||||
**Bot checkpoints:** `deaths=1`, `corpseCount=1`; после respawn игрок стоит на блоке около `playerY=676`; затем на первом уступе `playerY=490`, `playerX≥810`; на втором `playerY=510`, `playerX≥1430`; финально `completed=true`.
|
**Bot checkpoints:** `deaths=1`, `corpseCount=1`; после respawn игрок стоит на блоке около `playerY=676`; затем на первом уступе `playerY=490`, `playerX≥810`; на втором `playerY=510`, `playerX≥1430`; финально `completed=true`.
|
||||||
|
|
||||||
**Softlock/неопределённость:** маршрут зависит от возможности запрыгнуть на блок рядом с активным `FREEZER`, а затем набрать высоту до уступа; оба перехода ботом не проверены. Frozen block фиксирован детерминированно, поэтому физический дрейф тела не ожидается.
|
**Softlock/неопределённость:** первоначальная зона `FREEZER` перекрывала игрока, стоящего на блоке, на 26 px и делала intended route невозможным. Высота зоны минимально исправлена до `104`; floor runner всё ещё погибает, а оба прыжка подтверждены тремя запусками.
|
||||||
|
|
||||||
## Уровень 7 — `Live Wire`
|
## Уровень 7 — `Live Wire`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения с conductive corpse.
|
||||||
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
|
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
|
||||||
**Источник:** `LevelCatalog.level7()`; `ELECTRIC=(650,780,110,100)`, `relayId="power"`; дверь `(1130,560,84,320)` требует `"power"`; `goal=(1930,770,86,110)`.
|
**Источник:** `LevelCatalog.level7()`; `ELECTRIC=(650,780,110,100)`, `relayId="power"`; дверь `(1130,560,84,320)` требует `"power"`; `goal=(1930,770,86,110)`.
|
||||||
|
|
||||||
@@ -193,11 +193,11 @@ Door закрывает путь к goal
|
|||||||
|
|
||||||
**Bot checkpoints:** `deaths=1`, `corpseCount=1`; `allDoorsOpen=true` после появления conductive corpse; после respawn `playerX>1214`; финально `completed=true`, `deaths=1`. Для прямой проверки механики snapshot должен отдельно сообщать conductive state или активный `"power"`.
|
**Bot checkpoints:** `deaths=1`, `corpseCount=1`; `allDoorsOpen=true` после появления conductive corpse; после respawn `playerX>1214`; финально `completed=true`, `deaths=1`. Для прямой проверки механики snapshot должен отдельно сообщать conductive state или активный `"power"`.
|
||||||
|
|
||||||
**Softlock/неопределённость:** позиция relay-тела задаётся детерминированно, поэтому потеря сигнала не ожидается. Не доказано, что следующий игрок стабильно перепрыгивает совмещённые hazard/body без дополнительной смерти.
|
**Softlock/неопределённость:** relay-сигнал и прыжок через совмещённые hazard/body подтверждены тремя запусками ровно с одной смертью.
|
||||||
|
|
||||||
## Уровень 8 — `Borrowed Time`
|
## Уровень 8 — `Borrowed Time`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** intended route и recovery route доказаны ботом, каждый по 3/3 последовательных прохождения без смертей.
|
||||||
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
|
||||||
**Источник:** `LevelCatalog.level8()`; `TIMED=(560,810,90,70)`, `signalId="clock"`, duration `5.5`; moving platform `(990,770,240,34)`, travel `(380,0)`, period `4.0`, `controlId="clock"`; `PIT=(900,880,610,200)`; дверь `(1770,560,82,320)` требует `"clock"`; `goal=(2220,600,86,110)`.
|
**Источник:** `LevelCatalog.level8()`; `TIMED=(560,810,90,70)`, `signalId="clock"`, duration `5.5`; moving platform `(990,770,240,34)`, travel `(380,0)`, period `4.0`, `controlId="clock"`; `PIT=(900,880,610,200)`; дверь `(1770,560,82,320)` требует `"clock"`; `goal=(2220,600,86,110)`.
|
||||||
|
|
||||||
@@ -219,11 +219,11 @@ PIT разрывает floor, а Door закрывает дальнейший п
|
|||||||
|
|
||||||
**Bot checkpoints:** `"clock"` active с remaining time; platform `x>990`; игрок получает её `supportIndex`; затем `playerX≥1510`; Door остаётся open при `playerX>1852`; финально `completed=true`, `deaths=0`. Требуется добавить в snapshot remaining time и координаты платформы.
|
**Bot checkpoints:** `"clock"` active с remaining time; platform `x>990`; игрок получает её `supportIndex`; затем `playerX≥1510`; Door остаётся open при `playerX>1852`; финально `completed=true`, `deaths=0`. Требуется добавить в snapshot remaining time и координаты платформы.
|
||||||
|
|
||||||
**Softlock/неопределённость:** если `"clock"` истечёт после пересечения PIT, Door закроется, платформа замрёт, а переключатель останется на левой стороне; это потенциальный blocker-softlock до restart. Достаточность 5.5 s и синхронизация прыжка на платформу ботом не доказаны.
|
**Softlock/неопределённость:** исходный blocker-softlock был воспроизведён после истечения `"clock"` справа от `PIT`. Добавлен высокий recovery relay того же сигнала, недоступный при обычном беге по floor; intended route и восстановление после истечения таймера отдельно подтверждены по три раза.
|
||||||
|
|
||||||
## Уровень 9 — `Two Good Reasons`
|
## Уровень 9 — `Two Good Reasons`
|
||||||
|
|
||||||
**Статус:** предполагаемый маршрут, ботом не доказан.
|
**Статус:** доказан ботом, 3/3 последовательных прохождения с AND из двух тел.
|
||||||
**Ожидаемые смерти:** `2` (`targetDeaths=2`).
|
**Ожидаемые смерти:** `2` (`targetDeaths=2`).
|
||||||
**Источник:** `LevelCatalog.level9()`; `SPIKES=(520,850,130,30)`; `PRESSURE=(510,842,160,38)`, `signalId="weight"`; `ELECTRIC=(910,780,110,100)`, `relayId="power"`; дверь `(1510,540,86,340)` требует `"weight"` и `"power"`; `goal=(2310,770,86,110)`.
|
**Источник:** `LevelCatalog.level9()`; `SPIKES=(520,850,130,30)`; `PRESSURE=(510,842,160,38)`, `signalId="weight"`; `ELECTRIC=(910,780,110,100)`, `relayId="power"`; дверь `(1510,540,86,340)` требует `"weight"` и `"power"`; `goal=(2310,770,86,110)`.
|
||||||
|
|
||||||
@@ -246,11 +246,11 @@ Final Door использует AND из "weight" и "power"
|
|||||||
|
|
||||||
**Bot checkpoints:** после первой смерти `deaths/corpses=1/1`, `"weight"=true`, Door ещё closed; после второй `2/2`, `"power"=true`, `allDoorsOpen=true`; после respawn `playerX>1596`; финально `completed=true`, `deaths=2`. Snapshot нужно расширить поимёнными сигналами, чтобы доказать AND, а не только открытие двери.
|
**Bot checkpoints:** после первой смерти `deaths/corpses=1/1`, `"weight"=true`, Door ещё closed; после второй `2/2`, `"power"=true`, `allDoorsOpen=true`; после respawn `playerX>1596`; финально `completed=true`, `deaths=2`. Snapshot нужно расширить поимёнными сигналами, чтобы доказать AND, а не только открытие двери.
|
||||||
|
|
||||||
**Softlock/неопределённость:** первое тело сохраняет `player.x` в момент контакта со `SPIKES`; нужно подтвердить, что его пересечение с узкой общей зоной PRESSURE стабильно. Если `"weight"` не удерживается, дополнительная spike-смерть может изменить счётчик, но не гарантирует предусмотренное решение.
|
**Softlock/неопределённость:** первая смерть стабильно удерживает `"weight"`, но дверь остаётся закрытой; вторая активирует `"power"` и открывает AND-door. Цепочка подтверждена тремя запусками ровно с двумя телами.
|
||||||
|
|
||||||
## Уровень 10 — `The Last Procession`
|
## Уровень 10 — `The Last Procession`
|
||||||
|
|
||||||
**Статус:** предполагаемый mastery-маршрут, ботом не доказан.
|
**Статус:** mastery-маршрут доказан ботом, 3/3 последовательных прохождения с тремя телами.
|
||||||
**Ожидаемые смерти:** `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"`; `PIT=(1880,880,260,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,750,100,130)`; `ELECTRIC=(1510,780,110,100)`, `relayId="power"`; moving platform `(1840,790,250,34)`, travel `(0,-410)`, period `5.0`, `controlId="lift"`; `PIT=(1880,880,260,200)`; дверь `(2310,310,82,570)` требует `"power"`; верхний уступ `(2420,440,680)`; `goal=(2880,330,86,110)`.
|
||||||
|
|
||||||
@@ -276,12 +276,12 @@ Goal расположен на верхнем уступе за PIT и закр
|
|||||||
|
|
||||||
**Bot checkpoints:** после SAW `deaths=1`, `"lift"=true`, platform `y<790`; после FREEZER `deaths=2` и наличие frozen corpse; после ELECTRIC `deaths=3`, `"power"=true`, `allDoorsOpen=true`; затем игрок на moving-platform `supportIndex`, в верхней фазе `playerY≤280`; после прыжка `playerX≥2420`, `playerY≤340`; финально `completed=true`. Snapshot должен различать типы тел, оба сигнала, platform position/support.
|
**Bot checkpoints:** после SAW `deaths=1`, `"lift"=true`, platform `y<790`; после FREEZER `deaths=2` и наличие frozen corpse; после ELECTRIC `deaths=3`, `"power"=true`, `allDoorsOpen=true`; затем игрок на moving-platform `supportIndex`, в верхней фазе `playerY≤280`; после прыжка `playerX≥2420`, `playerY≤340`; финально `completed=true`. Snapshot должен различать типы тел, оба сигнала, platform position/support.
|
||||||
|
|
||||||
**Softlock/неопределённость:** обязательность frozen corpse не следует однозначно из текущей геометрии — `FREEZER` потенциально можно перепрыгнуть, сохранив только две необходимые сигнальные жертвы. Доставка saw-тела на плиту зависит от динамического торможения, а прыжок с платформы к ledge имеет небольшой горизонтальный запас. Это два sequence-break/проходимость follow-up, которые нельзя считать решёнными до acceptance-бота.
|
**Softlock/неопределённость:** доставка saw-тела, три сохранённых тела, подъём и lift→ledge прыжок подтверждены тремя запусками. Обязательность frozen corpse всё ещё не доказана отрицательным тестом: возможный двухсмертный sequence break остаётся follow-up.
|
||||||
|
|
||||||
## Проверки, следующие из графов
|
## Проверки, следующие из графов
|
||||||
|
|
||||||
1. Обобщить `AutomationSnapshot`: поимённые сигналы, состояния/координаты платформ, `supportIndex`, типы и флаги тел.
|
1. Сохранять полный integrated device-regression: intended route уровней 1–10 по три раза и отдельный recovery route уровня 8.
|
||||||
2. Для уровней 1–4 и 6–10 реализовать production-input acceptance-ботов; каждый intended route выполнить три раза подряд.
|
2. Отдельно проверить предполагаемый sequence break уровня 2 и необязательность `FREEZER` на уровне 10.
|
||||||
3. Отдельно проверить предполагаемые sequence break уровня 2 и необязательность `FREEZER` на уровне 10.
|
3. Добавить параметризованные проверки restart из ключевых промежуточных состояний.
|
||||||
4. Проверить blocker-softlock уровня 8 после истечения `"clock"` справа от `PIT`.
|
4. Повторить bot matrix на дополнительных frame rates и поддерживаемых версиях Android.
|
||||||
5. Проверить узкие/динамические места: тело на плите уровней 3 и 9, прыжок lift→ledge уровней 4 и 10.
|
5. Провести ручные сессии этапа 2 и скорректировать подсказки/допуски без ослабления механик.
|
||||||
|
|||||||
Reference in New Issue
Block a user