Builds a Flame game in three gated sub-phases (scaffold/core loop, systems/components, UI/content) with hard gates and round-based feedback intake for iterative fixes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/flutter-flame-harness:flame-harness-generatorThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Phase 5 of the flutter-flame-harness pipeline. Builds the Flame game in three gated
Phase 5 of the flutter-flame-harness pipeline. Builds the Flame game in three gated
sub-phases (5a → 5b → 5c). Each sub-phase ends with a HARD GATE (flutter analyze zero
issues + flutter test all pass) before the next sub-phase begins. On round N > 1, reads
the previous evaluator feedback and fixes only the listed failures.
All file schemas (config.md, state.md, handoff/round-N-gen.md, feedback/round-N-qa.md)
and the phase transition table are defined in docs/harness-protocol.md — that document is
the single source of truth (§2 for state.md schema; §4 for handoff layout; §5 for feedback
layout; §7 for the generator → evaluator transition). Do not redefine schemas here.
Read docs/harness/state.md. Extract current_round (integer, ≥ 1).
When current_round is 1:
docs/harness/config.md (extract app_slug, app_name, bundle_id,
default_language, skip_admob).docs/harness/plans/*-prd.md, sort descending, take first).docs/harness/plans/*-design.md, sort descending, take first).docs/harness/contract.md. Confirm ## Status: AGREED is present; abort with
flame-harness-generator: contract not AGREED — run flame-harness-contract first if missing.checkpoint from state.md (see docs/harness-protocol.md §2). Re-entry: if a prior run
crashed/paused mid-phase, skip every sub-phase ≤ checkpoint (its HARD GATE already passed and its
output exists) and resume at the next one — e.g. checkpoint: 5a → start at 5b. If checkpoint is
"", start at 5a. This makes the generator safe to re-run without redoing (or colliding with)
completed work.When current_round is greater than 1:
docs/harness/feedback/round-<N-1>-qa.md (per docs/harness-protocol.md §5 layout).## Failed Criteria section to extract each failing criterion and its prescribed fix.docs/harness/feedback/round-<N-1>-qa.md does not exist, abort with:
flame-harness-generator: feedback file for round <N-1> not found — cannot determine fixes.Run flutter create. The Dart package name must be snake_case (lowercase + underscores, no
hyphens) — convert app_slug (e.g. swing-line → swing_line). This package name is separate from
the bundle id:
GAME=<projects-dir>/<app_slug>
if [ -d "$GAME/lib" ]; then
echo "project already exists — reusing (skipping flutter create)"
else
flutter create --org com.<company> --project-name <app_slug_snake_case> "$GAME"
fi
Idempotency: flutter create fails if the target already exists, which would break a resume or a
re-run of round 1. The guard above makes 5a.1 safe to re-enter — it reuses an existing scaffold instead
of hard-failing. (If the user passed --clean, rm -rf "$GAME" first, then create fresh.)
Bundle id — set it explicitly and IDENTICALLY on both platforms (do not trust the value
flutter create derives from the project name). flutter create builds the bundle id from
--org + project-name, which can leave underscores / case differences and can diverge between iOS
and Android. Force both to the canonical config.bundle_id (com.<company>.<id>, lowercase
[a-z0-9] only — see docs/harness-protocol.md):
PRODUCT_BUNDLE_IDENTIFIER = <bundle_id> in all three build configs
(Debug/Release/Profile) in ios/Runner.xcodeproj/project.pbxproj.applicationId and namespace = <bundle_id> in android/app/build.gradle.kts.PRODUCT_BUNDLE_IDENTIFIER == Android applicationId == config.bundle_id,
byte-for-byte (same case, no _, no -). The AdMob app and store records use this exact id.Important (per docs/harness-protocol.md): After flutter create succeeds, move the
docs/harness/ directory into the game project so all artifacts share one repository:
mv <projects-dir>/flutter-flame-harness/docs/harness \
<projects-dir>/<app_slug>/docs/harness
All subsequent reads/writes of harness files use the new path inside the game project.
Delete the generated counter demo to start from a clean slate:
rm <projects-dir>/<app_slug>/lib/main.dart
rm <projects-dir>/<app_slug>/test/widget_test.dart
These files will be replaced by the game implementation below.
Set the following fields in pubspec.yaml:
name: <app_slug> (from config.md)description: <app_name> (from config.md)publish_to: noneversion: 1.0.0+1 — explicit semver MAJOR.MINOR.PATCH+BUILD (App Store marketing version =
1.0.0, not 1.0; the build phase bumps the +BUILD on each upload).Add to dependencies (use the latest compatible versions; minimum versions shown):
dependencies:
flutter:
sdk: flutter
flame: ^1.37.0
flame_audio: ^2.0.0
google_mobile_ads: ^5.0.0
shared_preferences: ^2.0.0
flutter_secure_storage: ^9.0.0 # durable save: iOS Keychain (survives reinstall/device)
play_services_block_store: ^0.8.0 # durable save: Android Block Store (survives reinstall/device)
Add to dev_dependencies (image is used by tool/gen_icon.dart + tool/strip_bg.dart):
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter # core-loop integration test (5c.8)
flutter_lints: ^4.0.0
image: ^4.0.0
flutter_launcher_icons: ^0.14.0
flutter_native_splash: ^2.4.0
Add asset directories to flutter::
flutter:
assets:
- assets/images/
- assets/audio/
- assets/icons/
Run flutter pub get and confirm it exits 0.
Create the following directories (and assets/ directories):
lib/
game/
components/
systems/
data/
screens/
ui/
l10n/
assets/
images/
audio/
icons/
This structure matches the PRD's lib/ layout.
Create lib/game/game_config.dart. This file must centralize every tuning constant that
affects gameplay (speeds, spawn rates, scores, timings, physics). No magic numbers are
permitted in any other game file — all values must reference a constant in GameConfig.
Template:
// lib/game/game_config.dart
// All tuning constants for <app_name>. Edit here to tweak gameplay — do not
// hard-code numbers elsewhere.
abstract class GameConfig {
// Screen / world
static const double worldWidth = 360.0;
static const double worldHeight = 640.0;
// Player
static const double playerSpeed = 200.0;
static const double playerJumpForce = 500.0;
// Scoring
static const int scorePerEnemy = 10;
static const int scorePerDistance = 1;
// Difficulty
static const double initialSpawnInterval = 2.0; // seconds
static const double minSpawnInterval = 0.4;
static const double difficultyRampRate = 0.02; // per second
// Audio
static const double bgmVolume = 0.6;
static const double sfxVolume = 0.9;
// AdMob (non-credential tuning only — IDs come from config.md)
static const int adShowIntervalSeconds = 120;
}
Fill all values from the PRD's game-mechanics section. Add more constants as needed; remove any that the specific game does not use.
Create lib/game/game_state.dart:
// lib/game/game_state.dart
enum GameState {
menu,
playing,
paused,
gameOver,
// Add states as the PRD requires (e.g. levelComplete, shop)
}
Create lib/game/<app_slug>_game.dart. This is the root FlameGame (or FlameGame with
HasCollisionDetection when the PRD requires collision detection). Do not add the removed
HasTappables mixin — it was dropped in Flame 1.7 and will cause a compile error. Tap input is
handled via TapCallbacks on individual components (see §5a.8). It must:
GameState _state = GameState.menu; and a getter GameState get state.void startGame(), void pauseGame(), void resumeGame(), void gameOver() methods
that transition _state and show/hide Flutter overlays by name.onLoad() to load assets and add the background component (call super.onLoad() first).'menu', 'hud', 'pause', 'gameOver').docs/game-gotchas.md): if components need to query peers each frame,
compute the active lists once in the game root's update(dt) and have components read that
cache — never call world.children.whereType<X>() per-component per-frame. Reuse
static final Paint objects; don't recreate Paint/shaders/blur every frame.Wire up the input method specified in the PRD:
TapCallbacks mixin on the player component (preferred for mobile).DragCallbacks mixin.KeyboardEvents mixin on the game class.Create lib/main.dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flame/game.dart';
import 'game/<app_slug>_game.dart';
// import overlay screens here in 5c
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Lock to config.md `orientation` — portrait OR landscape, never both.
// (Native lock is also set in §5c.10 so there is no rotate-on-launch flash.)
await SystemChrome.setPreferredOrientations(
// portrait → [portraitUp, portraitDown]; landscape → [landscapeLeft, landscapeRight]
<DeviceOrientation>[/* fill from config.orientation */],
);
runApp(GameWidget(game: <AppSlugGame>()));
}
Replace <AppSlugGame> with the actual class name.
Startup order & lifecycle (required — see the Lifecycle patterns in docs/game-gotchas.md).
main() must run in this order: WidgetsFlutterBinding.ensureInitialized() →
await SharedPreferences.getInstance() (so synchronous prefs reads later work) → orientation →
(if ads) ATT-after-first-frame → UMP consent → MobileAds.initialize() → runApp(...).
Host the GameWidget in a StatefulWidget that implements WidgetsBindingObserver:
didChangeAppLifecycleState on paused/inactive → game.pauseEngine() + BGM pause; on
resumed → resume. Clean up (stop audio, dispose pools, cancel timers) on the game's
onDetach()/onRemove(). Gate stale input on resume with a _canInput flag.
Create test/game_config_test.dart:
import 'package:flutter_test/flutter_test.dart';
import 'package:<app_slug>/game/game_config.dart';
void main() {
test('GameConfig constants are positive', () {
expect(GameConfig.worldWidth, greaterThan(0));
expect(GameConfig.worldHeight, greaterThan(0));
expect(GameConfig.bgmVolume, inInclusiveRange(0.0, 1.0));
expect(GameConfig.sfxVolume, inInclusiveRange(0.0, 1.0));
});
}
Copy templates/ci.yml.template → .github/workflows/ci.yml (GitHub Actions running
flutter analyze --no-fatal-infos + flutter test on push/PR), mirroring the shipped games' CI.
The generated game is its own git repository — initialize it and commit as the build progresses, so the work has atomic history from the start (and the user can diff/revert per sub-phase).
flutter create already wrote a complete, standard Flutter .gitignore (it ignores build/,
.dart_tool/, IDE files, Pods, etc.). Do not overwrite it — append the harness's
credential/secret excludes on top, by appending templates/gitignore.template (covers secrets/,
*.jks/*.p8/*.p12, key.properties, play-store-key.json, **/google-services.json,
ios/Runner/Runner.entitlements, ios/fastlane/certs/). These guarantee the signing/API key files
the build phase copies into the game are never committed.
cd <projects-dir>/<app_slug>
git init -q
cat <plugin-templates>/gitignore.template >> .gitignore # harness credential/secret excludes
git add -A && git commit -q -m "chore: scaffold <app_name> (flame-harness 5a)"
<plugin-templates> is this plugin's templates/ directory. pubspec.lock is committed (this is
an app — reproducible builds), so never add it to .gitignore; git add -A includes it by design.
Use Conventional Commits and never add AI-authorship trailers (no Co-Authored-By, no
"Generated with…" line). Make one atomic commit at each sub-phase gate (5a/5b/5c) and one per
discrete fix on round N > 1.
After completing steps 5a.1–5a.12, run both commands and confirm both exit 0 before proceeding to Sub-phase 5b:
cd <projects-dir>/<app_slug>
flutter analyze
flutter test
flutter analyze must report 0 issues. flutter test must report 0 failures. When green,
commit: git add -A && git commit -q -m "feat: core loop scaffold passes analyze+test (5a)", then
write checkpoint: 5a to state.md (atomically) so a later re-entry skips 5a.
If either command fails, fix all reported issues before proceeding. Do not start 5b until this gate is green.
Sub-phase 5b implements all game entities, systems, and data catalogs as specified in the PRD. Start only after the 5a HARD GATE passes.
Create lib/game/components/player_component.dart. The player component must:
SpriteAnimationComponent (or SpriteComponent for static sprites).GameConfig for speed, jump force, and any other tunable values.For each enemy or obstacle type named in the PRD, create a separate file under
lib/game/components/. Each component:
SpriteAnimationComponent or SpriteComponent.GameConfig constants for speed and behaviour parameters.Create lib/game/systems/spawn_system.dart. This system:
GameConfig.add(SpawnSystem()).Create lib/game/systems/collision_system.dart (or integrate via CollisionCallbacks on
each component if simpler). Handle:
Create lib/game/systems/score_system.dart. This system:
int.void addScore(int points) and int get score.ValueNotifier update when score changes so the HUD can update.shared_preferences (key: highScore).Audio assets (default = code-synthesized, so the game always ships with sound). Copy
templates/build_audio.dart.template → tool/build_audio.dart, tune its notes/tempo to the game's
mood, and dart run tool/build_audio.dart to write assets/audio/*.wav (22 kHz mono 16-bit,
iOS-safe). If the design asset plan sourced real audio instead, use it (convert to WAV per
docs/game-gotchas.md). Either way the game must reference only audio files that exist.
Create lib/game/systems/audio_system.dart. Follow the Audio patterns in
docs/game-gotchas.md (cite it; do not restate). Concretely this system:
AudioPool for each frequent SFX in onLoad() (1–3 players); playSfx(name)
calls pool.start(). Rare one-offs may use FlameAudio.play().debugPrint — a missing or
bad audio asset must never crash; playback is skipped silently.FlameAudio.bgm.play(...) only in the playing state; FlameAudio.bgm.stop() on game-over,
on app-background, and in onRemove()/onDetach().GameConfig (e.g. bgmVolume, sfxVolume are safe caps;
user slider is a fraction of the cap), and respects mute toggles in shared_preferences
(bgmEnabled, sfxEnabled).Create lib/systems/haptics.dart — a pure-Dart haptics helper per the Haptics patterns in
docs/game-gotchas.md. Gameplay never calls HapticFeedback.* directly. The helper:
!kIsWeb && (Platform.isIOS || Platform.isAndroid).Haptics.enabled toggle, and wraps every call in try/catch.light(), medium(), heavy()) over HapticFeedback.(Optional, for haptic-heavy games: a native iOS UIImpactFeedbackGenerator MethodChannel — see
docs/game-gotchas.md. Baseline Dart is sufficient otherwise.)
Create lib/game/systems/difficulty_system.dart. This system:
GameConfig.difficultyRampRate and
GameConfig.minSpawnInterval.Create data files under lib/game/data/ for each data-driven element the PRD defines
(enemies, levels, waves, collectibles, shop items). Each catalog is a Dart list of plain
data objects. No enemy/level/wave values may be hardcoded in spawning or component logic —
all values must come from the catalog.
Scaffold real system tests from templates/test_system.dart.template (copy to
test/systems_test.dart and adapt to this game's systems). Write at least 3 focused unit tests
covering the game's actual systems, e.g.:
ScoreSystem.addScore increments correctly (and reset zeroes it).DifficultySystem increases spawn rate over time but never below minSpawnInterval.These are the minimum; add one per non-trivial system the PRD defines (matches the shipped games, which carry 8–21 unit tests). Contract gate R11 requires ≥3 system unit tests.
After completing steps 5b.1–5b.9, run:
cd <projects-dir>/<app_slug>
flutter analyze
flutter test
flutter analyze must report 0 issues. flutter test must report 0 failures. When green,
commit: git add -A && git commit -q -m "feat: game systems and components (5b)", then write
checkpoint: 5b to state.md.
If either command fails, fix all reported issues before proceeding. Do not start 5c until this gate is green.
Sub-phase 5c wires up all Flutter screens and overlays, adds l10n, applies design tokens,
and completes shared_preferences persistence. Start only after the 5b HARD GATE passes.
Create lib/ui/design_tokens.dart from the design doc's ## Design tokens section, using
the exact file template specified in the design doc. Every colour, spacing, and radius value
must come from this file — no raw hex or numeric literals in UI code.
For each screen the PRD requires, create a file under lib/screens/. Each screen is a
StatefulWidget or StatelessWidget that receives the game instance and calls its state
transition methods. Required screens (add or remove based on PRD):
| File | Overlay name | Trigger |
|---|---|---|
lib/screens/main_menu_screen.dart | 'menu' | game starts |
lib/screens/hud_screen.dart | 'hud' | game playing |
lib/screens/pause_screen.dart | 'pause' | pause button tapped |
lib/screens/game_over_screen.dart | 'gameOver' | player dies |
lib/screens/settings_screen.dart | 'settings' | settings button |
If the PRD defines a shop, add lib/screens/shop_screen.dart with overlay name 'shop'.
Register all overlay builder functions in main.dart's GameWidget.overlayBuilderMap.
Accessibility baseline (gate R10 — see docs/game-gotchas.md → Accessibility & safety): every
tappable control on these menu/overlay screens must (1) be at least 48×48 dp, and (2) carry a
Semantics(label: …) (or a Tooltip/labelled widget) — especially icon-only buttons (pause,
settings, restart) — so the menus are screen-reader navigable. Read the OS Reduce Motion setting
via MediaQuery.of(context).disableAnimations and expose it (e.g. through the settings/prefs service)
so gameplay effects can damp screen-shake/particles/flashing; never flash faster than 3×/second.
Create lib/ui/hud.dart (a Flutter widget overlay, not a Flame component). The HUD must
display the current score, health or lives (if the PRD defines them), and a pause button.
All sizes and colours use DesignTokens constants.
Create ARB files:
lib/l10n/app_<default_language>.arb — primary-language strings (default_language); add app_en.arb as a secondary locale when default_language ≠ en.lib/l10n/app_en.arb — English strings.Every user-visible string in every screen and overlay must have an ARB entry. No string
literals in widget code — use AppLocalizations.of(context).<key>.
Add to pubspec.yaml:
flutter:
generate: true
dependencies:
flutter_localizations:
sdk: flutter
intl: ^0.19.0
Verify l10n is complete:
cd <projects-dir>/<app_slug>
flutter gen-l10n
Persist high score, BGM/SFX enabled flags, and any economy totals through a single
PreferencesService class. No raw SharedPreferences calls outside that class.
Persistence is durable by default — saves survive app reinstall and moving to a new device,
not just shared_preferences (which iOS drops on delete). Mirror the shipped games' proven pattern:
templates/save_repository.dart.template → lib/data/save_repository.dart, replacing
__SAVE_KEY__ with <app_slug>_save_v1. It stores one JSON blob across three tiers —
iOS Keychain (flutter_secure_storage, first_unlock), Android Block Store
(play_services_block_store), and a shared_preferences mirror — reading durable-first, writing
all tiers, every call in try/catch so a backend failure degrades instead of crashing.PreferencesService is backed by SaveRepository: load readMap() once at startup into an
in-memory map; each setter mutates the map and writes SaveRepository.write(jsonEncode(map)).
All getters/setters go through it — no raw SharedPreferences and no direct SaveRepository
calls elsewhere.This is not real-time cloud sync — it's last-write-wins restore on a fresh install/device.
See docs/game-gotchas.md (Persistence). Gated R9.
If skip_admob is false:
MobileAds.instance.initialize() in main.dart before runApp.config.md — never hardcode in Dart source.lib/ui/banner_ad_widget.dart.Before the final HARD GATE, run:
grep -rn "TODO\|stub\|placeholder\|스텁\|미구현" \
<projects-dir>/<app_slug>/lib/ --include="*.dart"
If this command returns any output, fix or remove every match. The contract requires zero
stubs in game logic (per docs/harness-protocol.md §3, Hard Gate 3).
Scaffold the two higher-level tests (contract gate R11 requires ≥1 widget test + ≥1 integration test), then expand coverage:
templates/widget_test.dart.template → test/menu_widget_test.dart; adapt
to the real main-menu screen. Assert it renders and its Play control (Semantics-labelled, ≥48dp per
R10) is tappable and triggers game start.templates/integration_test.dart.template →
integration_test/app_test.dart; adapt to drive the core loop (boot → play → game-over) and assert
no exception. (Requires the integration_test dev dep added in 5a.3.)app_<locale>.arb (default_language, plus app_en.arb when default_language ≠ en).PreferencesService read/write round-trip (use a mock SharedPreferences).A shipped game must not have the default Flutter icon, default splash, or a slug-ish name. Follow
the Build/platform + Store rejections patterns in docs/game-gotchas.md.
templates/gen_icon.dart.template → tool/gen_icon.dart,
fill its color constants from design_tokens (Background / Primary / Accent RGB) and kGlyph
from the first letter of app_name, add image: ^4.0.0 to dev_dependencies, then
dart run tool/gen_icon.dart → writes assets/icons/icon.png (1024×1024, opaque/no-alpha),
assets/images/splash.png, assets/store/play_icon.png (512×512 Play hi-res icon), and
assets/store/feature_graphic.png (1024×500 Play feature graphic). The screenshot phase places
the last two into the Android listing. If the design asset plan chose AI-generated art, use that image
instead at assets/icons/icon.png, flattened to opaque (no alpha).flutter_launcher_icons (with remove_alpha_ios: true,
image_path: assets/icons/icon.png) and flutter_native_splash (color: = design Background,
image: assets/images/splash.png) blocks to pubspec.yaml (per the design doc), then run:
dart run flutter_launcher_icons
dart run flutter_native_splash:create
app_name as the home-screen name: iOS CFBundleDisplayName
(base Info.plist + per-locale <locale>.lproj/InfoPlist.strings, register in the Xcode project);
Android android:label="@string/app_name" + values/strings.xml (+ values-<locale>/strings.xml)
for default_language and English. Not "Runner" / not the slug.Apply per docs/game-gotchas.md (Build/platform + Store rejections):
config.orientation.
Info.plist UISupportedInterfaceOrientations = only the chosen set (portrait →
UIInterfaceOrientationPortrait; landscape → …LandscapeLeft + …LandscapeRight), and remove
UISupportedInterfaceOrientations~ipad.<activity> set android:screenOrientation="portrait" (or
"sensorLandscape" for landscape).
This removes the other orientation entirely, so the app opens directly in its orientation with no
rotate-on-launch flash (setPreferredOrientations in main.dart alone is not enough).TARGETED_DEVICE_FAMILY = 1 (iPhone-only) in ios/Runner.xcodeproj
(both build configs).ITSAppUsesNonExemptEncryption = false to ios/Runner/Info.plist
(skips the per-upload export-compliance prompt).PopScope(canPop: false, ...): if an
overlay is open, close it; otherwise show a Flutter SnackBar ("뒤로 한 번 더 누르면 종료" /
"Press back again to exit", localized) and only exit on a second back within ~2 s. (In-game, back
= pause — see game-gotchas.)Default: code-drawn visuals — render players/enemies/UI with CustomPainter / Flame shapes using
the design_tokens palette (zero external image files, always renders). Only if the design asset plan
chose sprite art (AI-generated or a free/CC0 pack): obtain the images, then run
tool/strip_bg.dart (copy from templates/strip_bg.dart.template) to flood-fill the background to
alpha, and place the cleaned PNGs under assets/images/. Every asset path referenced in code or
declared in pubspec.yaml must exist on disk — no dangling references (a missing asset is a
runtime crash / blank). The harness never depends on un-sourced art: if nothing was sourced, the game
ships fully code-drawn.
A shipped game has a README.md and a LICENSE (all the reference games do). Create both at the game
project root:
README.md — from templates/README.md.template, filling: <app_name> (title + one-line pitch
from the PRD), a short gameplay summary, the tech stack (Flame + the actual pubspec.yaml deps:
audio, ads, l10n, durable save), and run/test/build commands. Add one asset-credits line stating the
default sourcing (audio = code-synthesized, visuals = code-drawn) — adjust if sprites were sourced.
Author it in the project's default_language (add an English section when it isn't en).LICENSE — from templates/LICENSE.template (MIT), with Copyright (c) <year> <company> taken
from config.developer (company, current year). If the user prefers a different license, honour it.Commit them with the 5c gate commit below.
After completing steps 5c.1–5c.11, run:
cd <projects-dir>/<app_slug>
flutter analyze
flutter test
flutter analyze must report 0 issues. flutter test must report 0 failures. Also confirm
branding (5c.9): a custom icon + splash were generated (not the default Flutter art), the icon is
opaque (no alpha), and the native display name equals app_name (not "Runner"/slug). And native
config (5c.10): orientation locked natively to config.orientation (unused one removed), iPhone-only
(TARGETED_DEVICE_FAMILY = 1), ITSAppUsesNonExemptEncryption = false, root back-button SnackBar.
And assets/CI (5b.6 / 5c.11 / 5a.11): the game ships with synthesized (or sourced) audio and
code-drawn (or cleaned-sprite) visuals, no missing-asset references, and a
.github/workflows/ci.yml is present.
This is the final gate. Do not write the handoff until both commands pass. If either fails,
fix all reported issues and re-run both commands. When green, commit:
git add -A && git commit -q -m "feat: UI, content, branding and native config (5c)", then write
checkpoint: 5c to state.md before writing the handoff.
After the 5c HARD GATE passes, write the generator handoff and update pipeline state.
Create docs/harness/handoff/round-<N>-gen.md inside the game project, following the layout
defined in docs/harness-protocol.md §4:
# Generator Handoff — Round <N>
## What Was Built / Fixed
<!-- Bullet list of features implemented (round 1) or bugs fixed (round N>1). -->
## Contract Self-Assessment
| Criterion | Status | Notes |
|---|---|---|
| flutter analyze zero errors | DONE / PARTIAL / FAIL | … |
| flutter test zero failures | DONE / PARTIAL / FAIL | … |
| No TODO/stub in game logic | DONE / PARTIAL / FAIL | … |
| game_config.dart for all tuning | DONE / PARTIAL / FAIL | … |
| Content defined as data | DONE / PARTIAL / FAIL | … |
| l10n complete (all configured locales) | DONE / PARTIAL / FAIL | … |
| Core loop end-to-end | DONE / PARTIAL / FAIL | … |
| Zero crashes on simulator | DONE / PARTIAL / FAIL | … |
| <game-specific criterion> | DONE / PARTIAL / FAIL | … |
## Test Results
\`\`\`
<last 20 lines of flutter analyze output>
\`\`\`
\`\`\`
<last 20 lines of flutter test output>
\`\`\`
## Environment Detection
- Flutter version: <output of `flutter --version`>
- Dart version: <from above>
- Device/emulator used for smoke test: <name>
## Known Issues
<!-- List any known issues or deferred items. State "none" if clean. -->
Fill every section with actual output and real assessments. Do not leave placeholder text.
Update docs/harness/state.md per docs/harness-protocol.md §2 and the generator → evaluator
transition in §7. Per §7 rule 2, a successful phase completion sets status: running in the
same atomic write:
status: running
current_phase: generator
next_role: evaluator
updated_at: "<ISO-8601 UTC now>"
Leave current_round, created_at, resume_attempts, and all other keys unchanged. Use
Edit for a targeted update.
Note:
current_roundis incremented by the evaluator when it returns FAIL; the generator reads it but does not write it.
Append one row to docs/harness/pipeline-log.md per docs/harness-protocol.md §6:
| <ISO-8601 UTC now> | handoff | generator | round <N> built; analyze 0; test 0; next: evaluator |
contract.md is missing or does not contain ## Status: AGREED, abort immediately.flutter analyze issues or failing tests), stop at that
sub-phase, fix the failures, and re-run the gate. Do not advance to the next sub-phase.state.md to status: paused, pause_reason: manual_action.flutter create fails, abort immediately and do not proceed to 5a.3.flutter pub get fails, check pubspec.yaml for version conflicts and resolve before
continuing.npx claudepluginhub tjdrhs90/flutter-flame-harness --plugin flutter-flame-harnessBootstraps and drives a full Flutter/Flame game pipeline from idea to playable game, dispatching each phase skill. Use when starting or continuing a flame-harness run.
Orchestrates the full game-dev pipeline from brainstorm to ship, delegating to specialist skills with explicit checkpoints. Trigger on 'make me a game' or 'build a game'.
Guides video game development: brainstorm ideas, plan gameplay loops, choose engines like Unity/Godot/Three.js/Phaser, scaffold projects, add features, fix bugs, create assets.