The fragile version of Android game automation is a long linear macro:
move -> wait -> detect -> tap -> wait -> move -> repeat
It works until one screen takes longer, the character drifts, or a target appears while movement is still active. Then the macro no longer knows which assumption failed.
A recoverable design separates three responsibilities:
- Navigation follows a saved route in the background.
- Observation reads the current screen and runtime state.
- Handling temporarily owns input for a bounded task, then returns control.
The pattern below is based on the current LaiCai Flow Router contracts and a local tutorial recording that demonstrates a route loop with monster and gathering branches. It is intended for permitted QA, prototyping, accessibility, and personal automation. Respect game rules and do not use automation for cheating, abuse, or disruptive multi-account activity.
Model navigation as a service, not a step
A route should not be represented as hundreds of hard-coded directional actions. Treat it as a service with a saved Navigation Map, an active session, a state, and observable progress.
The current Router interface exposes values such as:
{
"mapId": "saved-map-id",
"state": "moving",
"progress": 0.42,
"position": {"x": 0.51, "y": 0.34},
"positionValid": true,
"confidence": 0.88
}
The exact values vary by run, but the shape makes an important design possible: orchestration can branch on evidence instead of elapsed time.
router.continue starts or resumes a saved Navigation Map. Reusing the same map is idempotent, and resuming after pause relocalizes before movement. A different map safely releases the previous movement session before starting the new one.
Use explicit state transitions
A small state machine is enough for many route workflows:
type RouteState =
| "idle"
| "moving"
| "paused"
| "completed"
| "stuck"
| "lost"
| "error";
The orchestration loop should decide from state plus observations:
status = router.status()
if status.state == completed:
router.stop()
finish()
if status.state in [lost, error]:
saveEvidence(status)
router.stop()
escalate()
observation = detectTargets()
if observation.hasAllowedTarget:
router.pause(positionFrom = observation.position)
handleTarget()
verifyPostcondition()
router.continue(mapId)
else:
continueObserving()
This is pseudocode, not a promise that every game exposes the same objects. Its value is the control boundary: status does not change movement, pause retains route context, continue resumes, and stop clears the session.
Pause before acting on a moving screen
The tutorial footage shows why. The route branch keeps the character moving, then a visual model detects a monster or a resource. If a tap happens while the screen is still translating, the target coordinate may already be stale.
Use this sequence:
- Detect the target.
- Check whether its point or rectangle is inside an allowed region.
- Pause navigation.
- Re-observe when the target is small, fast, or safety-sensitive.
- Run the handling subflow.
- Verify a concrete postcondition.
- Resume navigation.
The verification step is not optional. “The tap command returned” only proves that an input was sent. A meaningful postcondition could be that the target disappeared, a collection indicator changed, a dialog closed, or a new known state appeared.
Android's UI Automator API uses the same broad principle in a different layer: performActionAndWait ties an action to a condition, while wait returns when a condition is met or a timeout expires. Condition-based synchronization is more informative than sleeping for an arbitrary duration.
Keep retries local and bounded
Do not restart the entire route because one handling action failed. Retry at the narrowest layer that still has valid context.
| Failure | Valid context still available? | Recovery |
|---|---|---|
| Target moved after detection | Route session and screen state are valid | Re-detect once or twice while paused |
| Handling postcondition missing | Route session is valid | Save screenshot, abandon target, resume |
| Low route progress | Position still valid | Run bounded stuck recovery, relocalize |
| Position invalid | Route progress is uncertain | Stop movement, save evidence, require reacquisition or human review |
| Map changed | Old session invalid | Stop and start the correct saved map |
Each retry needs a maximum attempt count and an exit state. Unlimited retry loops turn one unknown screen into uncontrolled input.
Separate stuck time from handling time
Stuck detection should only accumulate while Router is actively holding movement. Pauses, waits, taps, and handling sequences should not count as failed route progress.
This distinction prevents a combat animation or collection delay from looking like a navigation obstruction. It also makes tuning more portable: you can adjust low, balanced, or high sensitivity without rewriting the orchestration flow.
When recovery is enabled, keep it Profile-scoped. A bounded joystick sequence, key, or macro can attempt to clear an obstruction, after which the route service relocalizes. Do not bury time-based dead reckoning inside the Continue node; movement without position evidence is exactly what recovery should avoid.
Save evidence as part of the transition
When a route moves to lost or error, capture the state before cleaning up:
- screenshot and visible minimap region;
- map ID and route state;
- progress, position-valid flag, and confidence;
- last successful observation and handling branch;
- device, resolution, orientation, and app version;
- recovery attempts already used.
Evidence makes failures reproducible. It also distinguishes a bad map asset from an unexpected popup, a device rendering difference, or an overly aggressive recovery threshold.
Test the handoff, not only the happy path
Most route demos prove that movement works. Production testing should prove that ownership changes safely.
Add cases for:
- target appears during movement;
- target becomes invalid after pause;
- pause is called without an active route;
- continue is called twice for the same map;
- another map is selected while the first is active;
- stop is called when already idle;
- route reaches completion;
- localization confidence collapses;
- a forbidden target is detected;
- recovery succeeds on the second bounded attempt.
The implementation is successful when every case ends in a known state with a useful artifact—not merely when the character eventually moves.
Takeaway
Recoverability comes from explicit contracts. Navigation owns route movement. Observation owns state gathering. Handling owns temporary actions. Pause and resume transfer ownership without losing context; stop clears context when the assumptions have changed.
That architecture is easier to debug than a giant macro and safer than blind retries. For the wider node set and saved-profile workflow, the LaiCai Flow guide shows how visual, data, control, and device nodes fit together.
Top comments (0)