The third phase compares the UI navigation graph of the BEFORE and AFTER APKs. It detects screens that exist in only one state and edges that appear or disappear, labelling them as regression, addition, or no-change.
phase1/before/, phase1/after/ — shadow copies from Phase 1.reactivecircus/android-emulator-runner in CI).phase3/
├── before.utg/ # DroidBot UTG, BEFORE APK
├── after.utg/ # DroidBot UTG, AFTER APK
└── ui_regressions.json
./gradlew :<android-module>:assembleDebug. If this fails, mark Phase 3 BLOCKED — APK assembly failed (BEFORE) and stop.count = 100, timeout = 90, policy = dfs_greedy, -grant_perm, -is_emulator. Persist the UTG to phase3/before.utg/.phase3/after.utg/.{
"status": "completed",
"blocked_reason": "",
"before_screens": ["MainActivity", "DetailActivity"],
"after_screens": ["MainActivity", "DetailActivity"],
"diffs": [],
"activity_coverage_before": { "MainActivity": 18, "DetailActivity": 6 },
"activity_coverage_after": { "MainActivity": 17, "DetailActivity": 7 },
"nodes_before": 14, "nodes_after": 14,
"structures_before": 6, "structures_after": 6
}
On failure the same model carries a blocked status and an empty payload:
{
"status": "blocked",
"blocked_reason": "DroidBot produced no UTG artifact",
"before_screens": [],
"after_screens": [],
"diffs": []
}
--skip-dynamic. In CI, the emulator is provided by reactivecircus/android-emulator-runner.Phase 4 reads phase2/impact_graph.json and phase3/ui_regressions.json and produces the canonical consolidated.json.