Повысить устойчивость восьмого уровня к задержкам

This commit is contained in:
2026-08-18 03:40:40 +03:00
parent 9e418df184
commit de0f8b891f
3 changed files with 29 additions and 9 deletions
@@ -43,19 +43,39 @@ public final class LevelEightPlaythroughTest {
}
}
@Test
public void intendedRouteSurvivesStressedDecisionScheduling() {
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
LevelEightScenario.Result result = new LevelEightScenario()
.completeIntendedRoute(driver(activity, 96L), 20_000L);
assertTrue("Stressed scheduling: " + result.diagnostic(), result.completed());
assertEquals("The stressed intended route is deathless", 0, result.deaths());
}
}
private static AndroidDriver driver(ActivityScenario<MainActivity> activity) {
return driver(activity, 0L);
}
private static AndroidDriver driver(ActivityScenario<MainActivity> activity,
long extraDecisionDelayMillis) {
final GameView[] view = new GameView[1];
activity.onActivity(mainActivity -> view[0] = mainActivity.gameViewForAutomation());
return new AndroidDriver(InstrumentationRegistry.getInstrumentation(), view[0]);
return new AndroidDriver(InstrumentationRegistry.getInstrumentation(), view[0],
extraDecisionDelayMillis);
}
private static final class AndroidDriver implements LevelPlaytestBot.Driver {
private final Instrumentation instrumentation;
private final GameView gameView;
private final long extraDecisionDelayMillis;
AndroidDriver(Instrumentation instrumentation, GameView gameView) {
AndroidDriver(Instrumentation instrumentation, GameView gameView,
long extraDecisionDelayMillis) {
this.instrumentation = instrumentation;
this.gameView = gameView;
this.extraDecisionDelayMillis = extraDecisionDelayMillis;
}
@Override
@@ -80,7 +100,7 @@ public final class LevelEightPlaythroughTest {
@Override
public void waitForNextDecision(long millis) {
try {
Thread.sleep(millis);
Thread.sleep(millis + extraDecisionDelayMillis);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new AssertionError("Level-8 bot interrupted", interrupted);
@@ -109,10 +109,10 @@ public final class LevelCatalog {
8, "Borrowed Time", "Jump for the far blue relay if the timed gate closes.",
2450, 130, 760, goal(2220, 600), 0, 38,
platforms(
floor(0, 900), floor(1510, 940), ledge(1980, 710, 470),
floor(0, 970), floor(1510, 940), ledge(1980, 710, 470),
moving(990, 770, 240, 34, 380, 0, 4.0f, "clock")
),
hazards(hazard(PIT, 900, 880, 610, 200)),
hazards(hazard(PIT, 970, 880, 540, 200)),
switches(
sw(TIMED, 560, 810, 90, 70, "clock", 6.0f, 1),
sw(TIMED, 1600, 650, 90, 70, "clock", 3.5f, 1)
+4 -4
View File
@@ -199,7 +199,7 @@ Door закрывает путь к goal
**Статус:** intended route и восстановление после истечения таймера доказаны production-input ботом, по три раза подряд.
**Ожидаемые смерти:** `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=(900,880,610,200)`; дверь `(1770,560,82,320)` требует `"clock"`; `goal=(2220,600,86,110)`.
**Источник:** `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)`.
### Solution graph 8
@@ -207,7 +207,7 @@ Door закрывает путь к goal
PIT разрывает floor, а Door закрывает дальнейший путь
→ войти в TIMED и запустить signal "clock" на 6.0 s
→ "clock" одновременно запускает horizontal moving platform и открывает Door
→ без задержки добежать до края floor x=900
→ без задержки добежать до края floor x=970
→ прыгнуть на moving platform и пересечь PIT
→ сойти на правый floor x≥1510
→ пройти Door x=1770..1852 до истечения "clock"
@@ -222,9 +222,9 @@ PIT разрывает floor, а Door закрывает дальнейший п
→ пройти 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` дают запас на проход двери, не меняя crossing или recovery.
**Механика и сигналы:** фронт контакта основного `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.
**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.
**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 после закрытия двери.
**Softlock/неопределённость:** прежний blocker устранён: после истечения `"clock"` справа от PIT высокий relay гарантирует restart-free повторное открытие двери. Relay не помогает слева от PIT и не отменяет timed-platform crossing. Оба пути проверены без прямой мутации состояния, teleport, auto-complete или restart.