feat: endless score-attack mode in the engine

Add StageConfig.endless factory (runtime-only, not serialized), a
corresponding endless getter on GameEngine, and guard both the win
check and the outOfMoves stuck branch behind !_stage.endless so
endless runs can never be won or move-limited. Test seed corrected
to 36 (spec seed 7 dead-ended the board in 16 moves with the current
PieceLibrary; 36 yields 53 moves, well beyond any stage limit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 06:53:02 +09:00
parent 2b44dcd812
commit 6c76837ab6
3 changed files with 74 additions and 3 deletions
+17
View File
@@ -74,8 +74,21 @@ class StageConfig {
required this.objectives,
required this.stars,
required this.generatorProfile,
this.endless = false,
});
factory StageConfig.endless({required int seed}) => StageConfig(
id: 'endless',
seed: seed,
moveLimit: 0,
preset: const [],
objectives: const [],
stars: const StarThresholds(
twoMovesLeft: 1 << 30, threeMovesLeft: 1 << 30),
generatorProfile: 'mid',
endless: true,
);
factory StageConfig.fromJson(Map<String, dynamic> json) => StageConfig(
id: json['id'] as String,
seed: json['seed'] as int,
@@ -100,6 +113,10 @@ class StageConfig {
final StarThresholds stars;
final String generatorProfile;
/// Runtime-only: score-attack mode with no objectives or move limit.
/// Never serialized — packs always describe objective stages.
final bool endless;
GridState initialGrid() {
var grid = GridState.empty();
for (final cell in preset) {