Завершить инженерный кандидат Stage 5
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class AudioSettingsPersistenceTest {
|
||||
@Test
|
||||
public void independentAudioVolumesSurviveActivityRecreation() {
|
||||
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||
activity.onActivity(current -> {
|
||||
GameView view = current.gameViewForAutomation();
|
||||
view.resetAudioSettingsForAutomation();
|
||||
view.selectNextMusicVolumeForAutomation();
|
||||
view.selectNextEffectsVolumeForAutomation();
|
||||
view.selectNextVoiceVolumeForAutomation();
|
||||
});
|
||||
|
||||
activity.recreate();
|
||||
|
||||
activity.onActivity(current -> {
|
||||
GameView view = current.gameViewForAutomation();
|
||||
AudioSettings settings = view.audioSettingsForAutomation();
|
||||
assertEquals(1f, settings.musicVolume(), 0f);
|
||||
assertEquals(0f, settings.effectsVolume(), 0f);
|
||||
assertEquals(0f, settings.voiceVolume(), 0f);
|
||||
view.openSettingsForAutomation();
|
||||
assertTrue(view.visibleLocalizedTextForAutomation().contains("Audio"));
|
||||
assertTrue(view.screenDescriptionForAutomation().contains("Music 100%"));
|
||||
view.resetAudioSettingsForAutomation();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
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 GameSessionRestoreTest {
|
||||
@Test
|
||||
public void recreationRestoresActivePuzzleBodiesTimersAndPausedSafety() {
|
||||
GameSessionSnapshot[] before = new GameSessionSnapshot[1];
|
||||
|
||||
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||
GameView[] view = new GameView[1];
|
||||
activity.onActivity(current -> {
|
||||
view[0] = current.gameViewForAutomation();
|
||||
view[0].enableDeterministicAutomationClock();
|
||||
view[0].startLevelForAutomation(5);
|
||||
view[0].setAutomationInput(false, true, false);
|
||||
for (int decision = 0; decision < 400
|
||||
&& view[0].automationSnapshot().deaths() == 0; decision++) {
|
||||
view[0].advanceDeterministicAutomation(16L);
|
||||
}
|
||||
assertEquals(1, view[0].automationSnapshot().deaths());
|
||||
assertEquals(1, view[0].automationSnapshot().corpseCount());
|
||||
view[0].setAutomationInput(false, false, false);
|
||||
before[0] = view[0].sessionSnapshotForAutomation();
|
||||
});
|
||||
|
||||
activity.recreate();
|
||||
|
||||
activity.onActivity(current -> {
|
||||
view[0] = current.gameViewForAutomation();
|
||||
GameSessionSnapshot after = view[0].sessionSnapshotForAutomation();
|
||||
assertEquals(before[0].levelNumber(), after.levelNumber());
|
||||
assertEquals(before[0].deaths(), after.deaths());
|
||||
assertEquals(before[0].elapsedSeconds(), after.elapsedSeconds(), 0.001f);
|
||||
assertEquals(before[0].player().x(), after.player().x(), 0.001f);
|
||||
assertEquals(before[0].player().alive(), after.player().alive());
|
||||
assertEquals(before[0].bodies().size(), after.bodies().size());
|
||||
assertEquals(before[0].bodies().get(0).deathType(),
|
||||
after.bodies().get(0).deathType());
|
||||
assertEquals(before[0].platformMotionTimes(), after.platformMotionTimes());
|
||||
assertEquals(before[0].timedSignals(), after.timedSignals());
|
||||
assertTrue(view[0].automationSnapshot().visiblyPaused());
|
||||
assertFalse(view[0].automationSnapshot().inputActive());
|
||||
current.onBackPressed();
|
||||
view[0].enableDeterministicAutomationClock();
|
||||
view[0].advanceDeterministicAutomation(1_000L);
|
||||
assertTrue(view[0].automationSnapshot().playerAlive());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyProgressMigratesInPlaceWithoutLosingCompletion() {
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
SharedPreferences legacy = context.getSharedPreferences(
|
||||
"project_sacrifice_progress_v1", Context.MODE_PRIVATE);
|
||||
legacy.edit().clear()
|
||||
.putInt("unlocked", 6)
|
||||
.putBoolean("level_5_completed", true)
|
||||
.putInt("level_5_deaths", 3)
|
||||
.putLong("level_5_time", 42_000L)
|
||||
.commit();
|
||||
|
||||
ProgressStore migrated = new ProgressStore(context);
|
||||
|
||||
assertEquals(ProgressStore.CURRENT_SAVE_VERSION, migrated.saveVersion());
|
||||
assertEquals(6, migrated.unlockedCount());
|
||||
assertTrue(migrated.isCompleted(5));
|
||||
assertEquals(3, migrated.bestDeaths(5));
|
||||
assertEquals(42_000L, migrated.bestTimeMs(5));
|
||||
legacy.edit().clear().commit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
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 StageFiveDeterminismTest {
|
||||
@Test
|
||||
public void allTenRecordedRoutesReplayThreeSequentialTimesUnderFiveMinutes() {
|
||||
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||
GameView[] view = new GameView[1];
|
||||
activity.onActivity(current -> {
|
||||
view[0] = current.gameViewForAutomation();
|
||||
view[0].enableDeterministicAutomationClock();
|
||||
});
|
||||
DeterministicDriver driver = new DeterministicDriver(
|
||||
InstrumentationRegistry.getInstrumentation(), view[0]);
|
||||
LevelPlaytestBot bot = new LevelPlaytestBot();
|
||||
DeterministicInputTape[] tapes = new DeterministicInputTape[10];
|
||||
String[] signatures = new String[10];
|
||||
|
||||
tapes[0] = record(bot, driver, EarlyLevelScenarios.levelOne(), 20_000L, 0, 0);
|
||||
signatures[0] = driver.terminalSignature();
|
||||
tapes[1] = record(bot, driver, EarlyLevelScenarios.levelTwo(), 30_000L, 3, 3);
|
||||
signatures[1] = driver.terminalSignature();
|
||||
tapes[2] = record(bot, driver, EarlyLevelScenarios.levelThree(), 25_000L, 1, 1);
|
||||
signatures[2] = driver.terminalSignature();
|
||||
tapes[3] = record(bot, driver, EarlyLevelScenarios.levelFour(), 30_000L, 0, 0);
|
||||
signatures[3] = driver.terminalSignature();
|
||||
BotReplayRecorder levelFive = new BotReplayRecorder(driver);
|
||||
LevelPlaytestBot.Result levelFiveResult = bot.proveLevelFive(levelFive, 25_000L);
|
||||
assertStandardResult(5, levelFiveResult, 1, 1);
|
||||
tapes[4] = levelFive.tape();
|
||||
signatures[4] = driver.terminalSignature();
|
||||
tapes[5] = record(bot, driver, LateLevelScenarios.levelSix(), 30_000L, 1, 1);
|
||||
signatures[5] = driver.terminalSignature();
|
||||
tapes[6] = record(bot, driver, LateLevelScenarios.levelSeven(), 25_000L, 1, 1);
|
||||
signatures[6] = driver.terminalSignature();
|
||||
|
||||
BotReplayRecorder levelEight = new BotReplayRecorder(driver);
|
||||
LevelEightScenario.Result levelEightResult =
|
||||
new LevelEightScenario().completeIntendedRoute(levelEight, 30_000L);
|
||||
assertTrue(levelEightResult.diagnostic(), levelEightResult.completed());
|
||||
assertEquals(0, levelEightResult.deaths());
|
||||
tapes[7] = levelEight.tape();
|
||||
signatures[7] = driver.terminalSignature();
|
||||
|
||||
tapes[8] = record(bot, driver, LateLevelScenarios.levelNine(), 35_000L, 2, 2);
|
||||
signatures[8] = driver.terminalSignature();
|
||||
tapes[9] = record(bot, driver, LateLevelScenarios.levelTen(), 70_000L, 3, 3);
|
||||
signatures[9] = driver.terminalSignature();
|
||||
|
||||
for (int pass = 1; pass <= 3; pass++) {
|
||||
long passStartedAt = System.currentTimeMillis();
|
||||
for (int level = 0; level < tapes.length; level++) {
|
||||
String replayed = tapes[level].play(driver);
|
||||
assertEquals("pass=" + pass + ", level=" + (level + 1)
|
||||
+ ", tape=" + tapes[level].id(),
|
||||
signatures[level], replayed);
|
||||
assertTrue("pass=" + pass + ", level=" + (level + 1),
|
||||
driver.snapshot().completed());
|
||||
}
|
||||
long elapsed = System.currentTimeMillis() - passStartedAt;
|
||||
assertTrue("Replay matrix " + pass + " exceeded 5 minutes: " + elapsed,
|
||||
elapsed < 300_000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DeterministicInputTape record(
|
||||
LevelPlaytestBot bot, DeterministicDriver driver,
|
||||
LevelPlaytestBot.Scenario scenario, long budget,
|
||||
int expectedDeaths, int expectedBodies) {
|
||||
BotReplayRecorder recorder = new BotReplayRecorder(driver);
|
||||
LevelPlaytestBot.Result result = bot.run(recorder, scenario, budget);
|
||||
DeterministicInputTape tape = recorder.tape();
|
||||
assertStandardResult(tape.levelNumber(), result, expectedDeaths, expectedBodies);
|
||||
return tape;
|
||||
}
|
||||
|
||||
private static void assertStandardResult(int level, LevelPlaytestBot.Result result,
|
||||
int expectedDeaths, int expectedBodies) {
|
||||
assertTrue("level=" + level + "; " + result.diagnostic(), result.completed());
|
||||
assertEquals("level=" + level + "; deaths", expectedDeaths, result.deaths());
|
||||
assertEquals("level=" + level + "; bodies", expectedBodies, result.corpseCount());
|
||||
}
|
||||
|
||||
private static final class DeterministicDriver implements LevelPlaytestBot.Driver,
|
||||
DeterministicInputTape.Target {
|
||||
private final Instrumentation instrumentation;
|
||||
private final GameView gameView;
|
||||
private long simulationClockMillis;
|
||||
private boolean pendingLeft;
|
||||
private boolean pendingRight;
|
||||
private boolean pendingJump;
|
||||
private boolean inputDirty;
|
||||
private GameView.AutomationSnapshot cachedSnapshot;
|
||||
|
||||
DeterministicDriver(Instrumentation instrumentation, GameView gameView) {
|
||||
this.instrumentation = instrumentation;
|
||||
this.gameView = gameView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startLevel(int oneBasedLevelNumber) {
|
||||
simulationClockMillis = 0L;
|
||||
instrumentation.runOnMainSync(() -> {
|
||||
gameView.enableDeterministicAutomationClock();
|
||||
gameView.startLevelForAutomation(oneBasedLevelNumber);
|
||||
cachedSnapshot = gameView.automationSnapshot();
|
||||
});
|
||||
pendingLeft = pendingRight = pendingJump = false;
|
||||
inputDirty = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(boolean left, boolean right, boolean jump) {
|
||||
pendingLeft = left;
|
||||
pendingRight = right;
|
||||
pendingJump = jump;
|
||||
inputDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameView.AutomationSnapshot snapshot() {
|
||||
if (inputDirty) {
|
||||
instrumentation.runOnMainSync(() -> {
|
||||
gameView.setAutomationInput(pendingLeft, pendingRight, pendingJump);
|
||||
cachedSnapshot = gameView.automationSnapshot();
|
||||
});
|
||||
inputDirty = false;
|
||||
}
|
||||
return cachedSnapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForNextDecision(long millis) { advance(millis); }
|
||||
|
||||
@Override
|
||||
public void advance(long milliseconds) {
|
||||
simulationClockMillis += milliseconds;
|
||||
instrumentation.runOnMainSync(() -> {
|
||||
if (inputDirty) {
|
||||
gameView.setAutomationInput(pendingLeft, pendingRight, pendingJump);
|
||||
inputDirty = false;
|
||||
}
|
||||
gameView.advanceDeterministicAutomation(milliseconds);
|
||||
cachedSnapshot = gameView.automationSnapshot();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentTimeMillis() { return simulationClockMillis; }
|
||||
|
||||
@Override
|
||||
public String terminalSignature() {
|
||||
String[] signature = new String[1];
|
||||
instrumentation.runOnMainSync(() -> {
|
||||
if (inputDirty) {
|
||||
gameView.setAutomationInput(pendingLeft, pendingRight, pendingJump);
|
||||
inputDirty = false;
|
||||
}
|
||||
cachedSnapshot = gameView.automationSnapshot();
|
||||
signature[0] = gameView.deterministicStateSignatureForAutomation();
|
||||
});
|
||||
return signature[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class StageFivePerformanceTest {
|
||||
@Test
|
||||
public void levelTenRendererPublishesAndMeetsTheReleaseBudget() throws Exception {
|
||||
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||
GameView[] view = new GameView[1];
|
||||
activity.onActivity(current -> {
|
||||
view[0] = current.gameViewForAutomation();
|
||||
view[0].startLevelForAutomation(10);
|
||||
});
|
||||
|
||||
// Activity launch includes landscape rotation and shader warm-up. Cold-start has a
|
||||
// separate release gate; this contract measures steady-state gameplay rendering.
|
||||
Thread.sleep(5_000L);
|
||||
activity.onActivity(current -> view[0].resetPerformanceMetricsForAutomation());
|
||||
Thread.sleep(10_000L);
|
||||
PerformanceMonitor.Snapshot[] measured = new PerformanceMonitor.Snapshot[1];
|
||||
activity.onActivity(current -> measured[0] =
|
||||
view[0].performanceSnapshotForAutomation());
|
||||
PerformanceMonitor.Snapshot metrics = measured[0];
|
||||
assertTrue("At least 180 uninterrupted frames are required; samples="
|
||||
+ metrics.sampleCount(), metrics.sampleCount() >= 180);
|
||||
PerformanceBudget budget = PerformanceBudget.release();
|
||||
assertTrue(metrics.failureSummary(budget), metrics.within(budget));
|
||||
assertTrue("Target FPS must be explicit", budget.targetFramesPerSecond() >= 60);
|
||||
assertTrue("Runtime body cap must fit the published budget",
|
||||
PhysicsConfig.release().maximumBodies() <= budget.maximumBodies());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user