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

This commit is contained in:
2026-08-18 17:32:01 +03:00
parent da3777b4cc
commit f07d1cf830
5 changed files with 204 additions and 57 deletions
@@ -3,6 +3,9 @@ package com.projectsacrifice.game;
/** Black-box level-8 scenarios driven only through the production input boundary. */
final class LevelEightScenario {
private static final long DECISION_MILLIS = 32L;
private static final float LAUNCH_PLATFORM_X = 1_150f;
private String lastStep = "Start level 8";
static final class Result {
private final boolean completed;
@@ -21,145 +24,161 @@ final class LevelEightScenario {
}
Result recoverAfterClockExpires(LevelPlaytestBot.Driver driver, long timeoutMillis) {
long deadline = driver.currentTimeMillis() + timeoutMillis;
driver.startLevel(8);
GameView.AutomationSnapshot snapshot = crossPit(driver, deadline);
if (failed(snapshot, deadline, driver)) {
return failure("Cross the pit before the clock expires", snapshot);
GameView.AutomationSnapshot snapshot = driver.snapshot();
PlaytestSimulationBudget budget = PlaytestSimulationBudget.start(
snapshot.elapsedMilliseconds(), driver.currentTimeMillis(), timeoutMillis);
snapshot = crossPit(driver, budget);
if (failed(snapshot, budget, driver)) {
return failure("Cross the pit before the clock expires", snapshot, budget, driver);
}
snapshot = await(driver, deadline, "Wait for the far-side trap",
snapshot = await(driver, budget, "Wait for the far-side trap",
LevelPlaytestBot.Input.none(),
current -> current.playerX() >= 1_510f && !current.allDoorsOpen());
if (failed(snapshot, deadline, driver)) {
return failure("Wait for the far-side trap", snapshot);
if (failed(snapshot, budget, driver)) {
return failure("Wait for the far-side trap", snapshot, budget, driver);
}
snapshot = await(driver, deadline, "Return beneath the recovery relay",
snapshot = await(driver, budget, "Return beneath the recovery relay",
LevelPlaytestBot.Input.left(),
current -> current.playerX() <= 1_680f && current.playerY() >= 779f);
if (failed(snapshot, deadline, driver)) {
return failure("Return beneath the recovery relay", snapshot);
if (failed(snapshot, budget, driver)) {
return failure("Return beneath the recovery relay", snapshot, budget, driver);
}
snapshot = pulseLeftJumpThenAwait(driver, deadline, "Jump into the recovery relay",
snapshot = pulseLeftJumpThenAwait(driver, budget, "Jump into the recovery relay",
current -> current.allDoorsOpen());
if (failed(snapshot, deadline, driver)) {
return failure("Jump into the recovery relay", snapshot);
if (failed(snapshot, budget, driver)) {
return failure("Jump into the recovery relay", snapshot, budget, driver);
}
snapshot = await(driver, deadline, "Land after rearming the clock",
snapshot = await(driver, budget, "Land after rearming the clock",
LevelPlaytestBot.Input.none(), current -> current.playerY() >= 779f);
if (failed(snapshot, deadline, driver)) {
return failure("Land after rearming the clock", snapshot);
if (failed(snapshot, budget, driver)) {
return failure("Land after rearming the clock", snapshot, budget, driver);
}
return finishFromFarSide(driver, deadline, snapshot, "Finish after recovery");
return finishFromFarSide(driver, budget, snapshot, "Finish after recovery");
}
Result completeIntendedRoute(LevelPlaytestBot.Driver driver, long timeoutMillis) {
long deadline = driver.currentTimeMillis() + timeoutMillis;
driver.startLevel(8);
GameView.AutomationSnapshot snapshot = crossPit(driver, deadline);
if (failed(snapshot, deadline, driver)) {
return failure("Cross the pit before the clock expires", snapshot);
GameView.AutomationSnapshot snapshot = driver.snapshot();
PlaytestSimulationBudget budget = PlaytestSimulationBudget.start(
snapshot.elapsedMilliseconds(), driver.currentTimeMillis(), timeoutMillis);
snapshot = crossPit(driver, budget);
if (failed(snapshot, budget, driver)) {
return failure("Cross the pit before the clock expires", snapshot, budget, driver);
}
return finishFromFarSide(driver, deadline, snapshot, "Finish on the original timer");
return finishFromFarSide(driver, budget, snapshot, "Finish on the original timer");
}
private GameView.AutomationSnapshot crossPit(LevelPlaytestBot.Driver driver, long deadline) {
GameView.AutomationSnapshot snapshot = await(driver, deadline, "Trigger the left timer",
private GameView.AutomationSnapshot crossPit(LevelPlaytestBot.Driver driver,
PlaytestSimulationBudget budget) {
GameView.AutomationSnapshot snapshot = await(driver, budget, "Trigger the left timer",
LevelPlaytestBot.Input.right(), GameView.AutomationSnapshot::allDoorsOpen);
if (failed(snapshot, deadline, driver)) return snapshot;
if (failed(snapshot, budget, driver)) return snapshot;
long timerStartedAt = snapshot.elapsedMilliseconds();
snapshot = await(driver, deadline, "Approach the pit",
snapshot = await(driver, budget, "Approach the pit",
LevelPlaytestBot.Input.right(), current -> current.playerX() >= 760f);
if (failed(snapshot, deadline, driver)) return snapshot;
if (failed(snapshot, budget, driver)) return snapshot;
snapshot = await(driver, deadline, "Wait for the moving platform",
snapshot = await(driver, budget, "Wait for the moving platform",
LevelPlaytestBot.Input.none(),
current -> current.elapsedMilliseconds() >= timerStartedAt + 2_250L
&& current.firstMovingPlatformX() <= 1_050f);
if (failed(snapshot, deadline, driver)) return snapshot;
if (failed(snapshot, budget, driver)) return snapshot;
driver.setInput(false, true, true);
driver.waitForNextDecision(64L);
driver.setInput(false, true, false);
snapshot = await(driver, deadline, "Land on the moving platform",
snapshot = await(driver, budget, "Land on the moving platform",
LevelPlaytestBot.Input.right(),
GameView.AutomationSnapshot::playerOnMovingPlatform);
if (failed(snapshot, deadline, driver)) return snapshot;
if (failed(snapshot, budget, driver)) return snapshot;
snapshot = await(driver, budget, "Ride the moving platform to the launch zone",
LevelPlaytestBot.Input.none(),
current -> MovingPlatformLaunchPolicy.ready(current, LAUNCH_PLATFORM_X));
if (failed(snapshot, budget, driver)) return snapshot;
driver.setInput(false, true, true);
driver.waitForNextDecision(64L);
driver.setInput(false, true, false);
snapshot = await(driver, deadline, "Jump from the moving platform to the far floor",
snapshot = await(driver, budget, "Jump from the moving platform to the far floor",
LevelPlaytestBot.Input.right(), current -> current.playerX() >= 1_510f);
if (failed(snapshot, deadline, driver)) return snapshot;
snapshot = awaitFarFloorLandingOnOriginalTimer(driver, deadline);
if (failed(snapshot, deadline, driver)) return snapshot;
return await(driver, deadline, "Move under the relay after landing",
if (failed(snapshot, budget, driver)) return snapshot;
snapshot = awaitFarFloorLandingOnOriginalTimer(driver, budget);
if (failed(snapshot, budget, driver)) return snapshot;
return await(driver, budget, "Move under the relay after landing",
LevelPlaytestBot.Input.right(),
current -> current.playerX() >= 1_600f
&& current.playerY() >= 779f && current.allDoorsOpen());
}
private GameView.AutomationSnapshot awaitFarFloorLandingOnOriginalTimer(
LevelPlaytestBot.Driver driver, long deadline
LevelPlaytestBot.Driver driver, PlaytestSimulationBudget budget
) {
GameView.AutomationSnapshot snapshot = await(driver, deadline,
GameView.AutomationSnapshot snapshot = await(driver, budget,
"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",
if (failed(snapshot, budget, driver)) return snapshot;
return await(driver, budget, "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,
private Result finishFromFarSide(LevelPlaytestBot.Driver driver,
PlaytestSimulationBudget budget,
GameView.AutomationSnapshot snapshot, String step) {
snapshot = await(driver, deadline, "Reach the open-door jump",
snapshot = await(driver, budget, "Reach the open-door jump",
LevelPlaytestBot.Input.right(),
current -> current.playerX() >= 1_730f && current.allDoorsOpen());
if (failed(snapshot, deadline, driver)) {
return failure("Reach the open-door jump", snapshot);
if (failed(snapshot, budget, driver)) {
return failure("Reach the open-door jump", snapshot, budget, driver);
}
driver.setInput(false, true, true);
driver.waitForNextDecision(64L);
driver.setInput(false, true, false);
snapshot = await(driver, deadline, step, LevelPlaytestBot.Input.right(),
snapshot = await(driver, budget, step, LevelPlaytestBot.Input.right(),
GameView.AutomationSnapshot::completed);
driver.setInput(false, false, false);
if (!snapshot.completed()) return failure(step, snapshot);
return new Result(true, snapshot.deaths(), step + " completed; " + state(snapshot));
if (!snapshot.completed()) return failure(step, snapshot, budget, driver);
return new Result(true, snapshot.deaths(), step + " completed; "
+ budget.timingState(snapshot.elapsedMilliseconds(), driver.currentTimeMillis())
+ "; " + state(snapshot));
}
private GameView.AutomationSnapshot pulseLeftJumpThenAwait(
LevelPlaytestBot.Driver driver, long deadline, String step,
LevelPlaytestBot.Driver driver, PlaytestSimulationBudget budget, String step,
LevelPlaytestBot.SnapshotCondition completion
) {
driver.setInput(true, false, true);
driver.waitForNextDecision(64L);
driver.setInput(true, false, false);
return await(driver, deadline, step, LevelPlaytestBot.Input.left(), completion);
return await(driver, budget, step, LevelPlaytestBot.Input.left(), completion);
}
private GameView.AutomationSnapshot await(
LevelPlaytestBot.Driver driver, long deadline, String step,
LevelPlaytestBot.Driver driver, PlaytestSimulationBudget budget, String step,
LevelPlaytestBot.Input input, LevelPlaytestBot.SnapshotCondition completion
) {
lastStep = step;
GameView.AutomationSnapshot snapshot = driver.snapshot();
while (driver.currentTimeMillis() < deadline && snapshot.playerAlive()
while (!budget.exhausted(snapshot.elapsedMilliseconds(), driver.currentTimeMillis())
&& snapshot.playerAlive()
&& !snapshot.completed() && !completion.reached(snapshot)) {
setInput(driver, input);
driver.waitForNextDecision(DECISION_MILLIS);
snapshot = driver.snapshot();
}
if (driver.currentTimeMillis() >= deadline || !snapshot.playerAlive()) {
if (budget.exhausted(snapshot.elapsedMilliseconds(), driver.currentTimeMillis())
|| !snapshot.playerAlive()) {
driver.setInput(false, false, false);
}
return snapshot;
@@ -177,14 +196,20 @@ final class LevelEightScenario {
}
}
private static boolean failed(GameView.AutomationSnapshot snapshot, long deadline,
private static boolean failed(GameView.AutomationSnapshot snapshot,
PlaytestSimulationBudget budget,
LevelPlaytestBot.Driver driver) {
return !snapshot.completed()
&& (!snapshot.playerAlive() || driver.currentTimeMillis() >= deadline);
&& (!snapshot.playerAlive()
|| budget.exhausted(snapshot.elapsedMilliseconds(), driver.currentTimeMillis()));
}
private static Result failure(String step, GameView.AutomationSnapshot snapshot) {
return new Result(false, snapshot.deaths(), step + " failed; " + state(snapshot));
private Result failure(String step, GameView.AutomationSnapshot snapshot,
PlaytestSimulationBudget budget, LevelPlaytestBot.Driver driver) {
return new Result(false, snapshot.deaths(), step + " failed; lastStep=" + lastStep
+ "; " + budget.timingState(
snapshot.elapsedMilliseconds(), driver.currentTimeMillis())
+ "; " + state(snapshot));
}
private static String state(GameView.AutomationSnapshot snapshot) {
@@ -194,6 +219,11 @@ final class LevelEightScenario {
+ ", doorOpen=" + snapshot.allDoorsOpen()
+ ", completed=" + snapshot.completed()
+ ", elapsedMs=" + snapshot.elapsedMilliseconds()
+ ", deaths=" + snapshot.deaths();
+ ", deaths=" + snapshot.deaths()
+ ", platformX=" + snapshot.firstMovingPlatformX()
+ ", platformY=" + snapshot.firstMovingPlatformY()
+ ", onMovingPlatform=" + snapshot.playerOnMovingPlatform()
+ ", onGround=" + snapshot.playerOnGround()
+ ", platformPowered=" + snapshot.firstMovingPlatformPowered();
}
}