Завершить инженерную проверку этапа 2

This commit is contained in:
2026-08-18 13:54:48 +03:00
parent 3ad959dde0
commit 80822b74b1
17 changed files with 384 additions and 69 deletions
@@ -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 final Instrumentation instrumentation;
private final GameView gameView;
@@ -156,10 +156,27 @@ final class LateLevelScenarios {
.step("Clear the lift body again", 2_000L,
LevelPlaytestBot.Input.rightJump(),
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(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
.step("Jump over the frozen block", 2_500L,
snapshot -> !snapshot.inputActive())
.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(),
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
.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,
LevelPlaytestBot.Input.rightJump(),
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(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
.step("Clear the frozen block", 2_500L,
snapshot -> !snapshot.inputActive())
.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(),
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,
LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerX() >= 1_320f)
@@ -201,15 +240,23 @@ final class LateLevelScenarios {
.step("Clear the powered relay", 2_000L,
LevelPlaytestBot.Input.right(),
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(),
snapshot -> snapshot.playerAlive()
&& snapshot.playerY() >= 770f
&& !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,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 670f)
snapshot -> snapshot.playerAlive()
&& (snapshot.playerOnMovingPlatform()
|| snapshot.playerY() <= 670f))
.step("Board the moving lift", 2_000L,
LevelPlaytestBot.Input.right(),
GameView.AutomationSnapshot::playerOnMovingPlatform)
@@ -221,7 +268,7 @@ final class LateLevelScenarios {
LevelPlaytestBot.Input.right(),
snapshot -> snapshot.playerOnMovingPlatform()
&& 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(),
snapshot -> snapshot.playerY() <= 310f)
.step("Move to the raised lift edge", 2_000L,
@@ -236,4 +283,5 @@ final class LateLevelScenarios {
GameView.AutomationSnapshot::completed)
.build();
}
}
@@ -79,7 +79,8 @@ final class LevelEightScenario {
snapshot = await(driver, deadline, "Wait for the moving platform",
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;
driver.setInput(false, true, true);
@@ -87,7 +88,7 @@ final class LevelEightScenario {
driver.setInput(false, true, false);
snapshot = await(driver, deadline, "Land on the moving platform",
LevelPlaytestBot.Input.right(),
current -> current.elapsedMilliseconds() >= timerStartedAt + 3_200L);
GameView.AutomationSnapshot::playerOnMovingPlatform);
if (failed(snapshot, deadline, driver)) return snapshot;
driver.setInput(false, true, true);
@@ -107,18 +108,15 @@ final class LevelEightScenario {
private GameView.AutomationSnapshot awaitFarFloorLandingOnOriginalTimer(
LevelPlaytestBot.Driver driver, long deadline
) {
boolean moveLeft = true;
GameView.AutomationSnapshot snapshot = driver.snapshot();
while (driver.currentTimeMillis() < deadline && snapshot.playerAlive()
&& !(snapshot.playerX() >= 1_510f && snapshot.playerX() < 1_580f
&& snapshot.playerY() >= 779f && snapshot.allDoorsOpen())) {
driver.setInput(moveLeft, !moveLeft, false);
moveLeft = !moveLeft;
driver.waitForNextDecision(DECISION_MILLIS);
snapshot = driver.snapshot();
}
driver.setInput(false, false, false);
return snapshot;
GameView.AutomationSnapshot snapshot = await(driver, deadline,
"Land on the far floor", LevelPlaytestBot.Input.right(),
current -> current.playerX() >= 1_510f
&& current.playerY() >= 779f && current.allDoorsOpen());
if (failed(snapshot, deadline, driver)) return snapshot;
return await(driver, deadline, "Stabilize on the far floor",
LevelPlaytestBot.Input.left(),
current -> current.playerX() < 1_580f
&& current.playerY() >= 779f && current.allDoorsOpen());
}
private Result finishFromFarSide(LevelPlaytestBot.Driver driver, long deadline,