Pipeline Guide
About 1112 wordsAbout 4 min
Protocol Version
This project is generated from the create-maa-project pipeline template and uses the classic style: recognition / action are strings with parameters flattened at the node top level:
{
"NodeName": {
"recognition": "TemplateMatch",
"template": "button.png",
"threshold": 0.8,
"action": {"type": "Click"}
}
}This is structurally equivalent to the nested v2 style (recognition: {"type": ..., "param": {...}}) in the official docs. The runtime channel is MaaFramework stable (see the maafw field in maa-project.json; the version follows the channel automatically and the packaged runtime is authoritative); field validity is governed by the schemas in tools/schema/ and pnpm check:schema. For generic protocol details see the MaaFramework Pipeline Protocol.
Node Structure
Each pipeline node defines a recognition → action → transition step:
{
"NodeName": {
"recognition": "TemplateMatch",
"roi": [
100,
200,
80,
50
],
"template": "button.png",
"threshold": 0.8,
"action": {"type": "Click"},
"next": ["NextNode"],
"on_error": ["FallbackNode"]
}
}Recognition Types
| Type | Use Case | Description |
|---|---|---|
| TemplateMatch | Static UI | OpenCV template matching, images in image/, threshold 0.7; template accepts an array (any match hits — useful when an entry icon has multiple styles/states) |
| OCR | Dynamic text | PaddleOCR v5, expected supports regex, roi for text region |
| DirectHit | Routing | Always matches, used for next branching |
| Custom | Complex logic | Python custom recognition via @AgentServer.custom_recognition() |
| ColorMatch | Color filter | Used with OCR color_filter field for background removal |
Action Types
| Type | Description |
|---|---|
| Click | Click matched position. target: true for center, target: [x,y,w,h] for offset |
| DoNothing | Recognition-only, no action |
| Swipe | Swipe with param.begin / param.end / param.duration |
| Custom | Python custom action via @AgentServer.custom_action() |
Common Fields
| Field | Description |
|---|---|
pre_delay / post_delay | Wait before/after action (ms); avoid — prefer pre_wait_freezes / post_wait_freezes or intermediate recognition nodes |
post_wait_freezes | Wait until screen stops changing (smarter than fixed delay) |
max_hit | Max hits before skip. For looping UI elements |
timeout | Recognition timeout (ms), default 20000 |
only_rec | Recognition only, no action |
focus | Log notification on hit/failure |
color_filter | OCR color pre-filter, references a ColorMatch node |
Naming Conventions
- Use dot-separated hierarchy:
FarmResources.Start,ClaimRewards.CheckDaily - Prefix with module name:
PVP.,BattlePass.,Common. - JumpBack nodes must NOT have
next
Template Images & Asset Naming
Template images live under resource/base/image/:
- Folders: organize by pipeline module (e.g.
image/event_stage/,image/farm_resources/); shared images used by multiple modules go directly inimage/ - File names: lowercase
snake_case, e.g.flare_title.png,no_stamina.png,main_option.png - Server-specific images: put them under
resource/bilibili/image/orresource/taptap/image/(load order follows theresourcefield ininterface.json) - Making templates: crop from a 1280×720 screenshot at a moderate size (roughly 50×50 to 200×200); oversized templates are prone to false matches. See Overview for ROI and resolution baselines
- Paths: in pipeline JSON,
templateis a path relative to theimage/directory using forward slashes (e.g.event_stage/flare_title.png)
Comment & Placeholder Fields
Pipeline nodes support two kinds of comment/placeholder fields (already supported by schema):
doc/*_doc: node description*_code/code: placeholder for a required field, used when the template path is configured centrally ininterface.jsoninstead of being hardcoded in the pipeline
"EnterBattle": {
"doc": "Enter battle interface",
"template_code": "configure template via pipeline_override in interface.json",
"recognition": "TemplateMatch",
"roi": [885, 123, 340, 183],
"action": { "type": "Click" },
"next": ["CheckBattleInterface"]
}Why *_code placeholders?
TemplateMatch requires a template field, but if template paths are injected centrally via pipeline_override in interface.json (change once for resolution/server adaptation), there is nothing to fill in the pipeline file. template_code passes schema validation while telling developers "the template is configured elsewhere".
Design Patterns
Linear Flow
Best for sequential operations (e.g., game launch):
"LaunchGame": {
"recognition": "DirectHit",
"action": {
"type": "DoNothing",
"param": { "package": "com.phxh.official.nld" }
},
"next": ["ClickToStart"]
},
"ClickToStart": {
"recognition": "TemplateMatch",
"template": "click_to_start.png",
"action": { "type": "Click" },
"post_delay": 2000,
"next": ["DailyLoginReward", "CheckHomePage"]
}DirectHit Hub
"HubNode": {
"recognition": "DirectHit",
"action": { "type": "DoNothing" },
"next": ["BranchA", "BranchB"]
}next is OR logic: tries from top to bottom, executes the first match.
[JumpBack] Central Hub
Suitable for repeating sub-module visits (e.g., reward claim loop):
"ClaimRewards.MainHub": {
"recognition": "DirectHit",
"action": { "type": "DoNothing" },
"next": [
"[JumpBack]DispatchClaim.Start",
"[JumpBack]ClaimRewards.Start",
"[JumpBack]BattlePass.Start",
"[JumpBack]Mailbox.Start"
]
}[JumpBack] nodes return to the parent after execution. Only non-JumpBack nodes can exit the loop.
Battle Loop
"BattleStage": {
"recognition": "DirectHit",
"action": { "type": "DoNothing" },
"next": [
"[JumpBack]ClickVictory",
"[JumpBack]ClickItemDialog",
"QuickBattle"
]
}Task Option Override
tasks/*.json uses pipeline_override to modify node behavior at runtime:
"pipeline_override": {
"FarmResources.Start": {
"next": ["FarmResources.ResourceCollect"]
}
}Can override next, roi, threshold, custom_action_param, etc.
max_hit Anti-loop
"ClaimButton": {
"max_hit": 5,
...
}Max 5 hits before skip. Counts cross-session.
Negative Check (Parallel Candidates)
For a node meant to "stop when X is recognized, continue when it is not" (e.g., checking whether a stage is locked), do not rely on the node's own on_error for fallback: when the node is a candidate in a parent's next list, a recognition miss only means "this candidate did not match" — it does not trigger the candidate's own on_error. If the list has no further candidate, the whole list fails and retries, causing a timeout loop (verified in production: FarmResources.SelectSkillStage looped 21 attempts × 20s and failed the task).
The correct pattern is parallel candidates — put the "continue" node after the check node in the next list so it falls through naturally:
"SelectStage": {
"recognition": "DirectHit",
"next": [
"CheckLocked", // hit = locked, stop; miss = fall through to next candidate
"ClickStage" // continue branch (actually clicks into the stage)
]
},
"CheckLocked": {
"recognition": "TemplateMatch",
"template": "lock_icon.png",
// No on_error (does not fire in candidate position; keeping it only misleads)
"focus": {
"Node.Recognition.Succeeded": "Locked, stop",
"Node.Recognition.Failed": "Unlocked, continue"
}
}CheckLocked is evaluated once as a candidate; on miss the framework tries the next candidate (ClickStage). Only when all candidates miss does the whole list fail. Mark such nodes with "negative check (parallel candidates)" in desc.
Notes
- Prefer
wait_freezesover fixed delays — The screen may still be transitioning after a navigation click;post_wait_freezeswaits for the screen to settle, which is more reliable than a fixedpost_delay. Use fixed delays only when a loading animation cannot be frozen - OCR expected is regex —
".*"matches anything,"^text$"exact match - ROI at 1280x720 — coordinates
[x, y, w, h] - Fallback strategy — Put flow fallbacks (expected paths that don't work) at the end of
next; reserveon_errorfor genuine error states and mark with[错误兜底]. See the Fallback Strategy section above nextorder matters — highest priority first — Ordernextby priority: put nodes that must be excluded first (pop-ups, error dialogs) before normal branches. Counter-example: if a home-screen node (high match frequency) comes before a pop-up node, the pop-up case matches the home-screen node first and the flow gets stuck on the wrong screen. Within the same priority, sort by match frequency, fastest first
