Стабилизировать бота уровня 10 при задержках

This commit is contained in:
2026-08-18 03:33:53 +03:00
parent 6790a84d86
commit 9e418df184
4 changed files with 118 additions and 16 deletions
@@ -107,19 +107,56 @@ public final class LateLevelPlaythroughTest {
}
}
@Test
public void masteryProcessionSurvivesThrottledFinalRelayDecisions() {
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
GameDriver driver = GameDriver.withFinalRelayDelay(activity, 96L);
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
driver,
LateLevelScenarios.levelTen(),
65_000L
);
assertTrue(result.diagnostic(), result.completed());
assertEquals("throttling must not add accidental volunteers", 3, result.deaths());
assertEquals("all three intended bodies must remain", 3, result.corpseCount());
assertTrue("the conductive relay must still open the final door",
result.doorOpened());
}
}
private static final class GameDriver implements LevelPlaytestBot.Driver {
private final Instrumentation instrumentation;
private final GameView gameView;
private final long minimumDecisionMillis;
private final boolean throttleFinalRelayOnly;
private GameView.AutomationSnapshot lastSnapshot;
private GameDriver(Instrumentation instrumentation, GameView gameView) {
private GameDriver(Instrumentation instrumentation, GameView gameView,
long minimumDecisionMillis, boolean throttleFinalRelayOnly) {
this.instrumentation = instrumentation;
this.gameView = gameView;
this.minimumDecisionMillis = minimumDecisionMillis;
this.throttleFinalRelayOnly = throttleFinalRelayOnly;
}
static GameDriver from(ActivityScenario<MainActivity> activity) {
return create(activity, 32L, false);
}
static GameDriver withFinalRelayDelay(ActivityScenario<MainActivity> activity,
long minimumDecisionMillis) {
return create(activity, minimumDecisionMillis, true);
}
private static GameDriver create(ActivityScenario<MainActivity> activity,
long minimumDecisionMillis,
boolean throttleFinalRelayOnly) {
GameView[] view = new GameView[1];
activity.onActivity(current -> view[0] = current.gameViewForAutomation());
return new GameDriver(InstrumentationRegistry.getInstrumentation(), view[0]);
return new GameDriver(InstrumentationRegistry.getInstrumentation(), view[0],
minimumDecisionMillis, throttleFinalRelayOnly);
}
@Override
@@ -138,13 +175,22 @@ public final class LateLevelPlaythroughTest {
public GameView.AutomationSnapshot snapshot() {
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
return value[0];
lastSnapshot = value[0];
return lastSnapshot;
}
@Override
public void waitForNextDecision(long millis) {
try {
Thread.sleep(millis);
boolean inFinalRelayWindow = throttleFinalRelayOnly
&& lastSnapshot != null
&& lastSnapshot.levelNumber() == 10
&& lastSnapshot.deaths() == 3
&& lastSnapshot.playerX() >= 1_200f
&& lastSnapshot.playerX() <= 1_700f;
Thread.sleep(inFinalRelayWindow
? Math.max(millis, minimumDecisionMillis)
: millis);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new AssertionError("Late-level bot interrupted", interrupted);
@@ -155,10 +155,10 @@ final class LateLevelScenarios {
snapshot -> snapshot.playerX() >= 730f)
.step("Clear the lift body again", 2_000L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 900f)
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
.step("Prepare the frozen-block jump", 2_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 740f)
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
.step("Jump over the frozen block", 2_500L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
@@ -185,25 +185,42 @@ final class LateLevelScenarios {
snapshot -> snapshot.playerX() >= 730f)
.step("Clear the retained lift body", 2_000L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 900f)
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
.step("Prepare the final frozen-block jump", 2_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 740f)
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
.step("Clear the frozen block", 2_500L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
.step("Approach the powered relay", 2_500L,
LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerX() >= 1_390f)
.step("Clear the powered relay", 2_500L,
snapshot -> snapshot.playerX() >= 1_320f)
.step("Take off before the final relay", 1_000L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 700f)
.step("Clear the powered relay", 2_000L,
LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_650f)
.step("Approach the lift", 2_000L, LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerX() >= 1_730f)
.step("Board the moving lift", 3_000L,
.step("Wait for the lift at floor height", 6_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.playerAlive()
&& snapshot.playerY() >= 770f
&& !Float.isNaN(snapshot.firstMovingPlatformY())
&& snapshot.firstMovingPlatformY() >= 740f)
.step("Take off toward the moving lift", 1_000L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerX() >= 1_790f
&& snapshot.playerY() <= 670f)
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 670f)
.step("Board the moving lift", 2_000L,
LevelPlaytestBot.Input.right(),
GameView.AutomationSnapshot::playerOnMovingPlatform)
.step("Brake toward the lift center", 1_000L,
LevelPlaytestBot.Input.left(),
snapshot -> snapshot.playerOnMovingPlatform()
&& snapshot.playerX() <= 1_900f)
.step("Center on the moving lift", 1_000L,
LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerOnMovingPlatform()
&& snapshot.playerX() >= 1_920f)
.step("Ride the lift to the upper door", 5_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.playerY() <= 310f)