Завершить интеграционную устойчивость ботов этапа 3

This commit is contained in:
2026-08-18 18:27:33 +03:00
parent a8d72024f2
commit 9d792bdee4
8 changed files with 156 additions and 44 deletions
@@ -2,7 +2,6 @@ package com.projectsacrifice.game;
import android.app.Instrumentation;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -21,12 +20,16 @@ public final class GameLifecycleTest {
public void returningFromBackgroundKeepsPuzzleVisiblePausedAndClearsHeldInput() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
final MainActivity[] activity = new MainActivity[1];
final GameView[] view = new GameView[1];
scenario.onActivity(activity -> view[0] = activity.gameViewForAutomation());
scenario.onActivity(current -> {
activity[0] = current;
view[0] = current.gameViewForAutomation();
});
createRetainedCorpse(instrumentation, view[0]);
runOnGame(instrumentation, view[0], false, false, true, false);
scenario.moveToState(Lifecycle.State.STARTED);
instrumentation.runOnMainSync(activity[0]::onPause);
GameView.AutomationSnapshot backgroundStart = snapshot(instrumentation, view[0]);
sleep(700L);
GameView.AutomationSnapshot backgroundEnd = snapshot(instrumentation, view[0]);
@@ -37,7 +40,7 @@ public final class GameLifecycleTest {
assertEquals(backgroundStart.playerX(), backgroundEnd.playerX(), 0.01f);
assertEquals(backgroundStart.elapsedMilliseconds(), backgroundEnd.elapsedMilliseconds());
scenario.moveToState(Lifecycle.State.RESUMED);
instrumentation.runOnMainSync(activity[0]::onResume);
GameView.AutomationSnapshot returned = snapshot(instrumentation, view[0]);
assertTrue("Returning to an active puzzle must show the pause screen", returned.visiblyPaused());
assertFalse("Held touch input must not survive backgrounding", returned.inputActive());
@@ -20,10 +20,13 @@ final class LateLevelScenarios {
snapshot -> snapshot.playerX() >= 626f
&& snapshot.playerY() >= 668f
&& snapshot.playerY() <= 690f)
.step("Jump from the block to the first ledge", 2_500L,
.step("Land on the first ledge from the frozen block", 3_000L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerX() >= 750f
&& snapshot.playerY() <= 505f)
LevelPlaytestBot.whileDeathsAtMost(1,
snapshot -> snapshot.playerAlive()
&& snapshot.playerOnGround()
&& snapshot.playerX() >= 810f
&& snapshot.playerY() <= 500f))
.step("Cross the first ledge", 3_000L, LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerX() >= 1_230f)
.step("Jump the gap to the second ledge", 2_500L,
@@ -117,19 +120,24 @@ final class LateLevelScenarios {
LevelPlaytestBot.Input.right(),
snapshot -> snapshot.deaths() == 1
&& snapshot.corpseCount() == 1)
.step("Observe the delivered body power the lift", 3_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.deaths() == 1
&& snapshot.corpseCount() == 1
&& snapshot.firstMovingPlatformPowered())
.step("Wait for the freezer volunteer", 3_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() < 250f)
.step("Approach the launch saw", 2_500L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(1,
LevelPlaytestBot.groundedAliveAtOrBeyond(280f)))
LevelPlaytestBot.groundedAliveAtOrBeyond(320f)))
.step("Take off before the launch saw", 1_000L,
LevelPlaytestBot.Input.rightJump(),
LevelPlaytestBot.whileDeathsAtMost(1,
snapshot -> snapshot.playerAlive()
&& !snapshot.playerOnGround()
&& snapshot.playerY() <= 680f))
&& snapshot.playerY() <= 580f))
.step("Clear the launch saw", 2_000L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(1,
@@ -150,13 +158,13 @@ final class LateLevelScenarios {
.step("Return to the launch saw", 2_500L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(2,
LevelPlaytestBot.groundedAliveAtOrBeyond(280f)))
LevelPlaytestBot.groundedAliveAtOrBeyond(320f)))
.step("Take off before the saw again", 1_000L,
LevelPlaytestBot.Input.rightJump(),
LevelPlaytestBot.whileDeathsAtMost(2,
snapshot -> snapshot.playerAlive()
&& !snapshot.playerOnGround()
&& snapshot.playerY() <= 680f))
&& snapshot.playerY() <= 580f))
.step("Clear the saw again", 2_000L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(2,
@@ -203,13 +211,13 @@ final class LateLevelScenarios {
.step("Approach the saw for the final crossing", 2_500L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(3,
LevelPlaytestBot.groundedAliveAtOrBeyond(280f)))
LevelPlaytestBot.groundedAliveAtOrBeyond(320f)))
.step("Take off for the final saw crossing", 1_000L,
LevelPlaytestBot.Input.rightJump(),
LevelPlaytestBot.whileDeathsAtMost(3,
snapshot -> snapshot.playerAlive()
&& !snapshot.playerOnGround()
&& snapshot.playerY() <= 680f))
&& snapshot.playerY() <= 580f))
.step("Clear the saw for the final crossing", 2_000L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(3,
@@ -33,6 +33,14 @@ final class LevelEightScenario {
return failure("Cross the pit before the clock expires", snapshot, budget, driver);
}
snapshot = await(driver, budget, "Move clear of the platform sweep",
LevelPlaytestBot.Input.right(),
current -> current.playerX() >= 1_680f
&& current.playerY() >= 779f && current.allDoorsOpen());
if (failed(snapshot, budget, driver)) {
return failure("Move clear of the platform sweep", snapshot, budget, driver);
}
snapshot = await(driver, budget, "Wait for the far-side trap",
LevelPlaytestBot.Input.none(),
current -> current.playerX() >= 1_510f && !current.allDoorsOpen());
@@ -40,21 +48,21 @@ final class LevelEightScenario {
return failure("Wait for the far-side trap", snapshot, budget, driver);
}
snapshot = await(driver, budget, "Return beneath the recovery relay",
LevelPlaytestBot.Input.left(),
current -> current.playerX() <= 1_680f && current.playerY() >= 779f);
snapshot = positionBeneathRecoveryRelay(driver, budget);
if (failed(snapshot, budget, driver)) {
return failure("Return beneath the recovery relay", snapshot, budget, driver);
}
snapshot = pulseLeftJumpThenAwait(driver, budget, "Jump into the recovery relay",
snapshot = pulseRightJumpThenAwait(driver, budget, "Jump into the recovery relay",
current -> current.allDoorsOpen());
if (failed(snapshot, budget, driver)) {
return failure("Jump into the recovery relay", snapshot, budget, driver);
}
snapshot = await(driver, budget, "Land after rearming the clock",
LevelPlaytestBot.Input.none(), current -> current.playerY() >= 779f);
LevelPlaytestBot.Input.right(),
current -> current.playerX() >= 1_650f
&& current.playerY() >= 779f && current.allDoorsOpen());
if (failed(snapshot, budget, driver)) {
return failure("Land after rearming the clock", snapshot, budget, driver);
}
@@ -85,10 +93,10 @@ final class LevelEightScenario {
LevelPlaytestBot.Input.right(), current -> current.playerX() >= 760f);
if (failed(snapshot, budget, driver)) return snapshot;
snapshot = await(driver, budget, "Wait for the moving platform",
snapshot = await(driver, budget, "Wait for the outbound moving platform",
LevelPlaytestBot.Input.none(),
current -> current.elapsedMilliseconds() >= timerStartedAt + 2_250L
&& current.firstMovingPlatformX() <= 1_050f);
&& MovingPlatformLaunchPolicy.readyToBoard(current, 1_050f));
if (failed(snapshot, budget, driver)) return snapshot;
driver.setInput(false, true, true);
@@ -135,10 +143,8 @@ final class LevelEightScenario {
private Result finishFromFarSide(LevelPlaytestBot.Driver driver,
PlaytestSimulationBudget budget,
GameView.AutomationSnapshot snapshot, String step) {
snapshot = await(driver, budget, "Reach the open-door jump",
LevelPlaytestBot.Input.right(),
current -> current.playerX() >= 1_730f && current.allDoorsOpen());
if (failed(snapshot, budget, driver)) {
snapshot = positionForFinalJump(driver, budget);
if (failed(snapshot, budget, driver) || !snapshot.allDoorsOpen()) {
return failure("Reach the open-door jump", snapshot, budget, driver);
}
@@ -154,14 +160,62 @@ final class LevelEightScenario {
+ "; " + state(snapshot));
}
private GameView.AutomationSnapshot pulseLeftJumpThenAwait(
private GameView.AutomationSnapshot positionForFinalJump(
LevelPlaytestBot.Driver driver, PlaytestSimulationBudget budget
) {
lastStep = "Reach the open-door jump";
GameView.AutomationSnapshot snapshot = driver.snapshot();
while (!budget.exhausted(snapshot.elapsedMilliseconds(), driver.currentTimeMillis())
&& snapshot.playerAlive() && !snapshot.completed() && snapshot.allDoorsOpen()) {
if (snapshot.playerOnGround()
&& snapshot.playerX() >= 1_690f && snapshot.playerX() <= 1_730f) {
driver.setInput(false, false, false);
return snapshot;
}
if (snapshot.playerX() > 1_730f) {
driver.setInput(true, false, false);
} else {
driver.setInput(false, true, false);
}
driver.waitForNextDecision(DECISION_MILLIS);
snapshot = driver.snapshot();
}
driver.setInput(false, false, false);
return snapshot;
}
private GameView.AutomationSnapshot pulseRightJumpThenAwait(
LevelPlaytestBot.Driver driver, PlaytestSimulationBudget budget, String step,
LevelPlaytestBot.SnapshotCondition completion
) {
driver.setInput(true, false, true);
driver.setInput(false, true, true);
driver.waitForNextDecision(64L);
driver.setInput(true, false, false);
return await(driver, budget, step, LevelPlaytestBot.Input.left(), completion);
driver.setInput(false, true, false);
return await(driver, budget, step, LevelPlaytestBot.Input.right(), completion);
}
private GameView.AutomationSnapshot positionBeneathRecoveryRelay(
LevelPlaytestBot.Driver driver, PlaytestSimulationBudget budget
) {
lastStep = "Return beneath the recovery relay";
GameView.AutomationSnapshot snapshot = driver.snapshot();
while (!budget.exhausted(snapshot.elapsedMilliseconds(), driver.currentTimeMillis())
&& snapshot.playerAlive() && !snapshot.completed()) {
if (snapshot.playerOnGround()
&& snapshot.playerX() >= 1_620f && snapshot.playerX() <= 1_650f) {
driver.setInput(false, false, false);
return snapshot;
}
if (snapshot.playerX() > 1_650f) {
driver.setInput(true, false, false);
} else {
driver.setInput(false, true, false);
}
driver.waitForNextDecision(DECISION_MILLIS);
snapshot = driver.snapshot();
}
driver.setInput(false, false, false);
return snapshot;
}
private GameView.AutomationSnapshot await(
@@ -224,6 +278,7 @@ final class LevelEightScenario {
+ ", platformY=" + snapshot.firstMovingPlatformY()
+ ", onMovingPlatform=" + snapshot.playerOnMovingPlatform()
+ ", onGround=" + snapshot.playerOnGround()
+ ", platformPowered=" + snapshot.firstMovingPlatformPowered();
+ ", platformPowered=" + snapshot.firstMovingPlatformPowered()
+ ", platformMovingRight=" + snapshot.firstMovingPlatformMovingRight();
}
}