Стабилизировать посадку на лифт уровня 10

This commit is contained in:
2026-08-18 04:20:31 +03:00
parent de0f8b891f
commit a87e6cd62b
5 changed files with 50 additions and 22 deletions
@@ -108,9 +108,9 @@ public final class LateLevelPlaythroughTest {
}
@Test
public void masteryProcessionSurvivesThrottledFinalRelayDecisions() {
public void masteryProcessionSurvivesThrottledFinalTraversalDecisions() {
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
GameDriver driver = GameDriver.withFinalRelayDelay(activity, 96L);
GameDriver driver = GameDriver.withFinalTraversalDelay(activity, 96L);
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
driver,
@@ -130,33 +130,33 @@ public final class LateLevelPlaythroughTest {
private final Instrumentation instrumentation;
private final GameView gameView;
private final long minimumDecisionMillis;
private final boolean throttleFinalRelayOnly;
private final boolean throttleFinalTraversalOnly;
private GameView.AutomationSnapshot lastSnapshot;
private GameDriver(Instrumentation instrumentation, GameView gameView,
long minimumDecisionMillis, boolean throttleFinalRelayOnly) {
long minimumDecisionMillis, boolean throttleFinalTraversalOnly) {
this.instrumentation = instrumentation;
this.gameView = gameView;
this.minimumDecisionMillis = minimumDecisionMillis;
this.throttleFinalRelayOnly = throttleFinalRelayOnly;
this.throttleFinalTraversalOnly = throttleFinalTraversalOnly;
}
static GameDriver from(ActivityScenario<MainActivity> activity) {
return create(activity, 32L, false);
}
static GameDriver withFinalRelayDelay(ActivityScenario<MainActivity> activity,
long minimumDecisionMillis) {
static GameDriver withFinalTraversalDelay(ActivityScenario<MainActivity> activity,
long minimumDecisionMillis) {
return create(activity, minimumDecisionMillis, true);
}
private static GameDriver create(ActivityScenario<MainActivity> activity,
long minimumDecisionMillis,
boolean throttleFinalRelayOnly) {
boolean throttleFinalTraversalOnly) {
GameView[] view = new GameView[1];
activity.onActivity(current -> view[0] = current.gameViewForAutomation());
return new GameDriver(InstrumentationRegistry.getInstrumentation(), view[0],
minimumDecisionMillis, throttleFinalRelayOnly);
minimumDecisionMillis, throttleFinalTraversalOnly);
}
@Override
@@ -182,15 +182,14 @@ public final class LateLevelPlaythroughTest {
@Override
public void waitForNextDecision(long millis) {
try {
boolean inFinalRelayWindow = throttleFinalRelayOnly
boolean inFinalTraversalWindow = throttleFinalTraversalOnly
&& lastSnapshot != null
&& lastSnapshot.levelNumber() == 10
&& lastSnapshot.deaths() == 3
&& lastSnapshot.playerX() >= 1_200f
&& lastSnapshot.playerX() <= 1_700f;
Thread.sleep(inFinalRelayWindow
? Math.max(millis, minimumDecisionMillis)
: millis);
&& lastSnapshot.playerX() <= 2_200f;
Thread.sleep(inFinalTraversalWindow
? Math.max(millis, minimumDecisionMillis) : millis);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new AssertionError("Late-level bot interrupted", interrupted);
@@ -206,7 +206,7 @@ final class LateLevelScenarios {
snapshot -> snapshot.playerAlive()
&& snapshot.playerY() >= 770f
&& !Float.isNaN(snapshot.firstMovingPlatformY())
&& snapshot.firstMovingPlatformY() >= 740f)
&& snapshot.firstMovingPlatformY() >= 780f)
.step("Take off toward the moving lift", 1_000L,
LevelPlaytestBot.Input.rightJump(),
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 670f)
@@ -140,14 +140,14 @@ public final class LevelCatalog {
10, "The Last Procession", "Launch, freeze, conduct, ascend. Every lesson opens the last relic.",
3100, 130, 760, goal(2880, 330), 3, 120,
platforms(
floor(0, 1880), floor(2140, 960), ledge(2420, 440, 680),
floor(0, 1940), floor(2140, 960), ledge(2420, 440, 680),
moving(1840, 790, 250, 34, 0, -410, 5.0f, "lift")
),
hazards(
hazard(SAW, 520, 760, 96, 96, null, 700, -240),
hazard(FREEZER, 1110, 750, 100, 130),
hazard(ELECTRIC, 1510, 780, 110, 100, "power", 0, 0),
hazard(PIT, 1880, 880, 260, 200)
hazard(PIT, 1940, 880, 200, 200)
),
switches(sw(PRESSURE, 860, 860, 160, 20, "lift", 0, 1)),
doors(door(2310, 310, 82, 570, "power"))
@@ -100,6 +100,35 @@ public final class LevelCatalogTest {
freezer.box.x, freezer.box.y, freezer.box.width, freezer.box.height));
}
@Test
public void masteryLiftHasLaunchOverlapAndStillSpansARealPit() {
LevelDefinition level = LevelCatalog.get(9);
LevelDefinition.Platform launchFloor = level.platforms.get(0);
LevelDefinition.Platform landingFloor = level.platforms.get(1);
LevelDefinition.Platform lift = null;
LevelDefinition.Hazard pit = null;
for (LevelDefinition.Platform platform : level.platforms) {
if (platform.isMoving()) lift = platform;
}
for (LevelDefinition.Hazard hazard : level.hazards) {
if (hazard.type == LevelDefinition.HazardType.PIT) pit = hazard;
}
assertTrue("Level 10 must contain its moving lift", lift != null);
assertTrue("Level 10 must retain its final pit", pit != null);
float launchOverlap = Math.min(launchFloor.box.right(), lift.box.right())
- Math.max(launchFloor.box.x, lift.box.x);
assertTrue("The floor must overlap the lift by at least 100px for a stable launch",
launchOverlap >= 100f);
assertEquals("The pit must begin exactly where the launch floor ends",
launchFloor.box.right(), pit.box.x, 0f);
assertEquals("The pit must continue to the landing floor",
landingFloor.box.x, pit.box.right(), 0f);
assertTrue("The final pit must remain at least 200px wide", pit.box.width >= 200f);
assertTrue("The upper gallery must still require the vertical lift",
lift.travelY <= -400f && level.goal.y < launchFloor.box.y - 300f);
}
@Test
public void everyDoorAndControlledPlatformHasADeclaredSignalSource() {
for (LevelDefinition level : LevelCatalog.all()) {