How To Call It
Navigation is a global table: call its functions with a dot (Navigation.<method>(...)), not a colon. Requires Macro Handler 1.0.54 or later.
Saves a complete setup JSON document. Use expectedRevision=0 for creation and the read revision for updates. Author map, mini-map, marker, joystick and routes in code. Rejects stale revisions, foreign images and calibration writes. Does not start movement; geometry changes require recalibration.
Documented for app version 1.0.54 (not on Google Play yet)Updated:
Navigation is a global table: call its functions with a dot (Navigation.<method>(...)), not a colon. Requires Macro Handler 1.0.54 or later.
On 1.0.54 and later, author a setup without importing a plugin or adding a visual block. Images must be in the current macro’s image library. Replace the sample screen and route measurements with yours; RUN defaults to false, so the example starts no movement.
profileJson is the complete setup document. For creation use expectedRevision=0; an omitted id is generated. For an update, JSON.parse the readProfile result, retain id and revision, then write with that revision. Returns ok, code, profileId, revision and profileJson. Re-read after profile_revision_conflict.
liveMapRegion and miniMap.liveRegion use screen fractions from 0 to 1. Route points use reference-map fractions. joystick.radius is relative to the longer screen edge. marker anchorNorm and headingTipNorm are fractions of the marker crop. Limits: 16 setups, 32 routes per setup, 4 Mi characters of JSON and 4 MiB per stored setup. Rejects wrong types, unknown fields and images from other macros. calibration is not writable; geometry changes require new measurements. Writes and deletes are refused during an active journey.
It works best together with wait(), Point, SwipeParam, ClickParam, and sometimes a Region result.
The snippet below is a starter pattern that can be applied directly in runtime flow.
local r = Navigation.writeProfile(JSON.stringify({ label = "Coded setup" }), 0)
if r.ok then print(r.profileId, r.revision) else print(r.code) end
-- Fill mapReference, marker, joystick, regions and routes before running.From foundation to combined usage, each level is provided as a separate code block so you can copy the level you need and adapt it directly.
local r = Navigation.writeProfile(JSON.stringify({ label = "Coded setup" }), 0)
if r.ok then print(r.profileId, r.revision) else print(r.code) end
-- Fill mapReference, marker, joystick, regions and routes before running.local stepOk = true
local r = Navigation.writeProfile(JSON.stringify({ label = "Coded setup" }), 0)
if r.ok then print(r.profileId, r.revision) else print(r.code) end
-- Fill mapReference, marker, joystick, regions and routes before running.
if stepOk then
wait(200)
endlocal ok, err = pcall(function()
local r = Navigation.writeProfile(JSON.stringify({ label = "Coded setup" }), 0)
if r.ok then print(r.profileId, r.revision) else print(r.code) end
-- Fill mapReference, marker, joystick, regions and routes before running.
end)
if not ok then
print("API step failed: Navigation.writeProfile: " .. tostring(err))
requestStop()
end-- In automation flow, chain actions with short wait() intervals and use requestStop() or controlled retry on failure paths.
local function run_writeprofile_step()
local r = Navigation.writeProfile(JSON.stringify({ label = "Coded setup" }), 0)
if r.ok then print(r.profileId, r.revision) else print(r.code) end
-- Fill mapReference, marker, joystick, regions and routes before running.
end
local ok, err = pcall(run_writeprofile_step)
if not ok then
toast("Step failed")
print(err)
end-- Macro Handler 1.0.54+: no .mhblock import or visual block is needed.
-- Save map_image and marker_image in THIS macro's image library first.
-- These example coordinates are placeholders. Measure your own screen/map before enabling RUN.
local RUN = false
local mapResult = Navigation.asset("map_image")
local markerResult = Navigation.asset("marker_image")
assert(mapResult.ok, mapResult.code)
assert(markerResult.ok, markerResult.code)
local marker = {
mode = "REFERENCE", crop = markerResult.asset,
taughtSizeMapNormW = 0.04, taughtSizeMapNormH = 0.04,
anchorNorm = {x = 0.5, y = 0.5}, headingTipNorm = {x = 0.5, y = 0.1}
}
local routeId = "11111111-2222-4333-8444-555555555555"
local profile = {
label = "My coded navigation",
mapReference = {image = mapResult.asset},
frameWidth = 1200, frameHeight = 800,
liveMapRegion = {left = 0.1, top = 0.1, right = 0.8, bottom = 0.9},
marker = marker,
miniMap = {
liveRegion = {left = 0.80, top = 0.02, right = 0.98, bottom = 0.25},
shape = "circle", markerPosition = "centered", centerSearchRadius = 0.12,
marker = marker
},
joystick = {center = {x = 0.12, y = 0.80}, radius = 0.08, deadZone = 0.15},
routes = {{id = routeId, name = "Route A", type = "one_way",
points = {{x = 0.20, y = 0.30}, {x = 0.25, y = 0.40}, {x = 0.40, y = 0.50}}}}
}
-- Retain the returned profileId. Do not create a fresh profile on every macro iteration.
local profileKey = "my_coded_navigation_profile"
local previousId = KV.get(profileKey, "")
local expectedRevision = 0
if previousId ~= "" then
local previous = Navigation.readProfile(previousId)
assert(previous.ok, previous.code) -- Do not silently recreate a deleted/unknown setup.
profile.id = previous.profileId
profile.revision = previous.revision
expectedRevision = previous.revision
end
local saved = Navigation.writeProfile(JSON.stringify(profile), expectedRevision)
assert(saved.ok, saved.code)
KV.set(profileKey, saved.profileId)
print("Setup saved", saved.profileId, saved.revision)
-- An observation group can be an ordinary Lua function; no saved visual group is required.
local monitorId = "coded-monitor"
local function onCheckpoint(event)
assert(event.groupId == monitorId, "Unknown callback")
if event.validateOnly then return true end -- Preflight never performs screen actions.
if event.kind == "observation" then
print("Position", event.values.playerX, event.values.playerY)
-- Read-only image/OCR/color checks may be added here.
-- Before touching the screen, assert(Navigation.stop() == true).
end
return true
end
if RUN then
-- Enable only after reviewing YOUR coordinates, heading and target application.
local result = Navigation.run({
profileId = saved.profileId, routeId = routeId, mapSource = "mini_map",
routeType = "one_way", controlMode = "heading_feedback", timeoutMs = 120000,
headingFeedback = {fixedCamera = true, joystickUpHeadingDegrees = 0},
joystickMotion = {enabled = false, amplitudeDegrees = 5, periodMs = 1000},
observationGroup = {groupId = monitorId, groupName = "Code monitor", intervalMs = 1000,
executionMode = "observe"}
}, onCheckpoint)
print(result.state, result.lastFailure)
end