### Install Dependencies
Source: https://github.com/steve1316/uma-android-automation/blob/master/scripts/imageDetection/README.md
Install the necessary Python dependencies for the image detection utility. Ensure you are in the repository root.
```bash
pip install -r scripts/imageDetection/requirements.txt
```
--------------------------------
### Install Frontend Dependencies
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Run this command in the project's root directory to install all necessary frontend dependencies using Yarn.
```bash
yarn install
```
--------------------------------
### Compile and Install Android Build
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Use this command to compile the application and install it directly onto your Android device for testing purposes.
```bash
yarn android
```
--------------------------------
### Install Python Dependencies
Source: https://github.com/steve1316/uma-android-automation/blob/master/scripts/data-scraper/README.md
Installs the necessary Python packages listed in the requirements file. Ensure you are in the repository root directory before running.
```bash
pip install -r scripts/data-scraper/requirements.txt
```
--------------------------------
### Start Expo Metro Server
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Execute this command to start the Metro HTTP server for the React Native frontend. This is the recommended way to run the development environment.
```bash
yarn start
```
```bash
npx expo start
```
--------------------------------
### Get Race Grade Label
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Maps backend grade strings (e.g., 'G1', 'PRE_OP') to short labels for display in grade badges. Provides specific abbreviations for 'PRE_OP', 'FINALE', 'MAIDEN', and 'DEBUT'.
```javascript
/**
* Maps a backend grade string (e.g. "G1", "PRE_OP", "MAIDEN") to the short label
* shown inside the colored grade badge. Defaults to the input when unknown.
* @param {string} grade
*/
function calGradeLabel(grade) {
if (!grade) return ""
if (grade === "PRE_OP" || grade === "PRE-OP") return "Pre"
if (grade === "FINALE") return "Fin"
if (grade === "MAIDEN") return "Mdn"
if (grade === "DEBUT") return "Dbt"
return grade
}
```
--------------------------------
### Change Phone Resolution to 1080p via ADB
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Execute these ADB commands from a computer to set the device resolution to 1080p and DPI to 240. Ensure ADB is installed and your device is connected.
```bash
adb shell wm size 1080x1920
```
```bash
adb shell wm density 240
```
--------------------------------
### Initialization Functions
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Calls functions to initialize font size, toolbar, and connect to the application when the page loads.
```javascript
initFontSize()
initToolbar()
renderCalendar(null)
connect()
```
--------------------------------
### Start or Reset Refresh Timer
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Starts or resets an interval to update timer text. Clears any existing interval before setting a new one.
```javascript
RefreshTimer() // Start or reset the interval to update the timer text. if (refreshTimerInterval) clearInterval(refreshTimerInterval) refreshTimerInterval = setInterval(updateRefreshTimer, 5000)
```
--------------------------------
### Test and Visualize with test.py
Source: https://github.com/steve1316/uma-android-automation/blob/master/yolo/README.md
Launch the interactive visualization tool to test the model's performance on full-size images. It performs localized ROI inference, re-maps detections, and provides an interactive UI. The latest `best.pt` is auto-loaded from the `runs/` directory.
```powershell
python test.py
```
--------------------------------
### Generate Release APK
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Run this command to generate a release-ready APK file for the Android application.
```bash
yarn build
```
--------------------------------
### Training Analysis Pipeline Steps
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Outlines the process for analyzing training options, including stat gain detection, caching, and filtering based on failure chance.
```text
1. Iterate all 5 stats: For each stat, the bot clicks the corresponding training tab button.
2. Stat gain detection per training:
- Main stat gain and sub-stat gains (detected via template matching or optionally YOLO — see below)
- Failure chance percentage
- Relationship bar colors (blue, green, orange) for support card characters present
- Rainbow count (number of rainbow indicators)
- Skill hints available
3. Results are cached for the current turn in `cachedAnalysisResults` to avoid re-reading if the training screen is visited multiple times.
4. Filtering: Trainings exceeding the maximum failure chance threshold (default 20%) are excluded, unless risky training mode or Good-Luck Charm overrides are active.
```
--------------------------------
### Load and Render Log Files List
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Fetches the list of log files from the server, caches the response, and renders them in a list. Displays a status message during loading and handles errors.
```javascript
async function loadLogFiles() {
const listEl = document.getElementById("logFilesList")
const emptyEl = document.getElementById("logFilesEmpty")
const statusEl = document.getElementById("logFilesStatus")
const countEl = document.getElementById("logFilesCount")
const totalEl = document.getElementById("logFilesTotalSize")
statusEl.textContent = "Loading..."
try {
const res = await fetch("/logs/files")
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json()
const files = Array.isArray(data.files) ? data.files : []
logFilesCache = files
countEl.textContent = String(data.count || files.length)
totalEl.textContent = formatFileSize(data.totalSize || 0)
statusEl.textContent = ""
listEl.innerHTML = ""
if (files.length === 0) {
emptyEl.style.display = "block"
} else {
emptyEl.style.display = "none"
for (const file of files) {
listEl.appendChild(renderLogFileRow(file))
}
}
} catch (err) {
statusEl.textContent = "Failed to load log files."
console.error("loadLogFiles failed:", err)
}
}
```
--------------------------------
### Generate Training Data with crop_dataset.py
Source: https://github.com/steve1316/uma-android-automation/blob/master/yolo/README.md
Use this script to process raw screenshots into specialized crops for training the YOLO model. Ensure your raw screenshots are in the `images_original/` directory.
```powershell
python crop_dataset.py
```
--------------------------------
### Run Image Detection Script
Source: https://github.com/steve1316/uma-android-automation/blob/master/scripts/imageDetection/README.md
Execute the image detection script from the repository root. The script resolves paths relative to its own location.
```bash
python scripts/imageDetection/imageDetection.py
```
--------------------------------
### Initialize Toolbar Collapse State
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Loads the toolbar collapse state from local storage on initialization. Adds the 'collapsed' class if the toolbar was previously collapsed.
```javascript
function initToolbar() {
const isCollapsed = localStorage.getItem("toolbarCollapsed") === "true"
if (isCollapsed) {
document.getElementById("toolbar").classList.add("collapsed")
}
}
```
--------------------------------
### Run Data Scraper
Source: https://github.com/steve1316/uma-android-automation/blob/master/src/data/README.md
Execute the Python script to refresh the game data JSON files from the repository root.
```bash
python scripts/data-scraper/main.py
```
--------------------------------
### Open Log File for Inline Viewing
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Opens a modal to display a log file inline. Files larger than LOG_FILE_PREVIEW_BYTE_LIMIT show a placeholder to prevent browser freezing. Requires elements with ids 'logFileModal', 'logFileModalTitle', 'logFileModalBody', and 'logFileModalDownload'.
```javascript
async function openLogFile(name) {
const modal = document.getElementById("logFileModal")
const titleEl = document.getElementById("logFileModalTitle")
const bodyEl = document.getElementById("logFileModalBody")
const downloadEl = document.getElementById("logFileModalDownload")
titleEl.textContent = name
downloadEl.href
```
--------------------------------
### Format Codebase
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Use this command to format both TypeScript/TSX files with Prettier and Kotlin files with Ktlint, ensuring code consistency.
```bash
yarn format
```
--------------------------------
### Train YOLO Model with yolo.py
Source: https://github.com/steve1316/uma-android-automation/blob/master/yolo/README.md
Run the main training pipeline. This script automatically utilizes settings defined in `config.py`, including image size and region definitions.
```powershell
python yolo.py
```
--------------------------------
### Format Kotlin Files
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Execute this command to format only Kotlin files using Ktlint, adhering to the settings defined in android/.editorconfig.
```bash
yarn format:kt
```
--------------------------------
### UMA Bot Main Processing Loop
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
The main loop calls `process()` on each tick to determine the current screen and dispatch to the appropriate handler. Dialogs are always checked first. If no known screen is detected, the bot taps to progress.
```mermaid
flowchart TD
Start["process() called"] --> Dialogs{"Any dialogs
detected?"}
Dialogs -->|Yes| HandleDialog["Handle dialog
(close, confirm, etc.)"]
HandleDialog --> Return["Return null
(continue loop)"]
Dialogs -->|No| MainScreen{"On the
Main Screen?"}
MainScreen -->|Yes| HandleMain["handleMainScreen()
(full turn logic)"]
HandleMain --> Return
MainScreen -->|No| TrainingEvent{"Training Event
screen?"}
TrainingEvent -->|Yes| HandleEvent["handleTrainingEvent()
(select reward option)"]
HandleEvent --> Return
TrainingEvent -->|No| MandatoryRace{"Mandatory Race
Prep screen?"}
MandatoryRace -->|Yes| HandleMandatory["handleRaceEvents()
(enter mandatory race)"]
HandleMandatory --> Return
MandatoryRace -->|No| RacingScreen{"Already on
Racing screen?"}
RacingScreen -->|Yes| HandleRace["handleStandaloneRace()
(complete the race)"]
HandleRace --> Return
RacingScreen -->|No| EndScreen{"Career End
screen?"}
EndScreen -->|Yes| FinalUpdate["Purchase career-end skills
Read final fan count
Log final stats"]
FinalUpdate --> Complete["Return Success
(bot stops)"]
EndScreen -->|No| CampaignSpecific{"Campaign-specific
condition?"}
CampaignSpecific -->|Yes| HandleCampaign["Handle scenario logic
(e.g. Unity Cup race)"]
HandleCampaign --> Return
CampaignSpecific -->|No| Inheritance{"Inheritance
event?"}
Inheritance -->|Yes| HandleInherit["Accept inheritance"]
HandleInherit --> Return
Inheritance -->|No| Misc["Perform misc checks
or tap to progress"]
Misc --> Return
```
--------------------------------
### WebSocket Connection and Event Handlers
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Establishes a WebSocket connection and sets up handlers for open, message, close, and error events. It manages reconnection attempts and UI status updates.
```javascript
function connect() {
if (reconnectAttempts > 0) {
setStatus("connecting", "Reconnecting in 5...")
} else {
setStatus("connecting", "Connecting...")
}
// Reset sync state on fresh connection.
isSyncing = true
syncingBuffer = []
const protocol = location.protocol === "https:" ? "wss:" : "ws:"
const wsUrl = protocol + "//" + location.host
ws = new WebSocket(wsUrl)
ws.onopen = function () {
if (reconnectInterval) clearInterval(reconnectInterval)
setStatus("connected")
reconnectAttempts = 0
footerInfo.textContent = "Connected to " + location.host
// Clear the display on a fresh connection. This removes logs from
// a previous run, and prevents duplicate logs from being appended
// when the server resyncs the history buffer over this new connection.
clearDisplay()
}
ws.onmessage = function (event) {
if (event.data === "HISTORY_DONE") {
// History sync is finished. Process all buffered messages.
isSyncing = false
appendBatchMessages(syncingBuffer)
syncingBuffer = []
processAllHistoryForDashboard()
} else if (event.data === "CMD:CLEAR") {
// Received a signal from the server to clear the display for a new run.
clearDisplay()
} else if (event.data.startsWith("HB:")) {
// Received a batch of historical logs as a JSON array.
try {
var msgs = JSON.parse(event.data.substring(3))
if (isSyncing) {
syncingBuffer.push.apply(syncingBuffer, msgs)
} else {
appendBatchMessages(msgs)
}
} catch (e) {
console.error("Error parsing history batch:", e)
}
} else if (event.data.startsWith("CAL:")) {
// Received a Race History calendar snapshot.
try {
renderCalendar(JSON.parse(event.data.substring(4)))
} catch (e) {
console.error("Error parsing calendar snapshot:", e)
}
} else if (event.data.startsWith("SRS_STATE:")) {
// Smart Race Solver enabled flag pushed by Racing.kt at run start. Toggles
// the .srs-disabled class on the panel so it disappears when SRS is off.
setSmartRaceSolverEnabled(event.data.substring(10) === "true")
} else {
// Process individual log messages, images or unknown formats.
try {
var data = JSON.parse(event.data)
if (data.type === "image") {
handleIncomingImage(data)
} else if (data.type === "image_batch_done") {
handleImageBatchDone()
} else {
if (isSyncing) {
syncingBuffer.push(data)
} else {
appendLogMessage(data)
}
}
} catch (e) {
// Fallback for non-JSON messages if any.
console.warn("Received non-JSON message:", event.data)
}
}
}
ws.onclose = function () {
setStatus("disconnected")
scheduleReconnect()
}
ws.onerror = function () {
setStatus("disconnected")
}
}
```
--------------------------------
### Initialize Font Size from Preference
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Initializes the font size by retrieving it from localStorage. If no stored preference is found or if it's invalid, it uses a default value and updates the UI.
```javascript
function initFontSize() {
const storedSize = localStorage.getItem("uaa_log_font_size")
if (storedSize !== null) {
const parsedSize = parseFloat(storedSize)
if (!isNaN(parsedSize)) {
currentFontSize = parsedSize
}
}
updateFontSize(currentFontSize)
}
```
--------------------------------
### UMA Bot Main Screen Turn Orchestration
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
When on the Main Screen, `handleMainScreen()` orchestrates the turn by calling various functions for state updates, checks, and action execution. It includes scenario-specific hooks for entry and updates.
```mermaid
sequenceDiagram
participant Bot as Campaign
participant OCR as OCR Engine
participant State as Game State
Bot->>Bot: onBeforeMainScreenUpdate()
Note over Bot: Scenario hook (e.g. Trackblazer shop check)
Bot->>OCR: Read date string from screen
OCR-->>Bot: "Classic Year Early Feb"
Bot->>State: updateDate() → turn 27
Note over Bot: Date changed → reset daily flags
par 9 parallel OCR threads (10s timeout)
Bot->>OCR: Read Speed stat
Bot->>OCR: Read Stamina stat
Bot->>OCR: Read Power stat
Bot->>OCR: Read Guts stat
Bot->>OCR: Read Wit stat
Bot->>OCR: Read Skill Points
Bot->>OCR: Read Mood
Bot->>OCR: Read Energy
Bot->>OCR: Check racing requirements
end
Bot->>Bot: Update aptitudes (first time only)
Bot->>Bot: Update fan count (if needed)
Bot->>Bot: performGlobalChecks()
Bot->>Bot: onMainScreenEntry()
Note over Bot: Scenario hook (e.g. Trackblazer item usage)
Bot->>Bot: decideNextAction()
Bot->>Bot: executeAction()
```
--------------------------------
### Download Logs from Server
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Fetches logs from the server endpoint. If the server download fails, it falls back to reconstructing logs from available message data.
```javascript
function downloadLogs() {
const traineeName = document.getElementById("dashboardName").textContent.trim()
const datePart = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-") // Prefer the server-side download for full, non-truncated logs.
fetch("/logs/download")
.then((response) => {
if (!response.ok) throw new Error("Server download failed")
return response.blob()
})
.then((blob) => {
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
// Use a descriptive filename.
if (traineeName && traineeName !== "-") {
a.download = `${traineeName}_${datePart}.txt`
} else {
a.download = `log @ ${datePart}.txt`
}
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
})
.catch((err) => {
console.error("Download error:", err)
// Fallback: reconstruct raw log lines from pre-parsed objects.
const content = allMessages.map((m) => {
var line = ""
if (m.parsed.newline) line += m.parsed.newline
if (m.parsed.timestamp) line += m.parsed.timestamp + " "
if (m.parsed.level) line += "[" + m.parsed.level + "] "
line += m.parsed.message
return line
}).join("\n")
const blob = new Blob([content], { type: "text/plain" })
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `log @ ${datePart}.txt`
a.click()
URL.revokeObjectURL(url)
})
}
```
--------------------------------
### Show Training Breakdown Tooltip
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Displays a tooltip detailing the breakdown of training actions, including counts and levels for stats like Speed, Stamina, Power, Guts, and Wit.
```javascript
/**
* Shows a specific tooltip for the training action counter.
*/
function showTrainingTooltip(event) {
let content = '
'
const stats = ["Speed", "Stamina", "Power", "Guts", "Wit"]
stats.forEach(stat => {
const count = trainingBreakdown[stat] || 0
const level = trainingLevels[stat]
const levelText = level != null ? `
(Lvl ${level})` : ""
content += `
${stat} ${count}${levelText}
`
})
content += "
"
showTooltip(event, "Training Breakdown", content)
}
```
--------------------------------
### Ask the Docs Chatbot Flow
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Illustrates the data flow for the Ask the Docs chatbot, from user question to final answer mode. It shows the retrieval process, model generation, and verification steps.
```mermaid
graph TD
A[question] --> B(JS) Chat → bridge → searchDocs(q, 4)
B --> C{DocIndex top-k by cosine similarity}
C --> D{ChatOrchestrator.expandSection() reassembles full sections}
D --> E{no active model ─────────────────────────► retrieveOnly (verbatim)}
D --> F{llama.rn generates an answer}
F --> G{output == "NOT_IN_DOCS" ─────────────────► retrieveOnly (verbatim)}
F --> H{groundingVerifier.overlap()}
H --> I{overlap ≥ SUMMARY_THRESHOLD (0.3) ───────► generated (cite)}
H --> J{overlap < SUMMARY_THRESHOLD ────────────► verifierFallback (verbatim)
```
--------------------------------
### MILP Solver Backend
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
The MILP solver builds an ExpressionsBasedModel using ojAlgo to mirror a GLPK formulation. It defines decision variables for race vs train, specific race choices, completion indicators, and consecutive race indicators.
```kotlin
SmartRaceSolver.solve(state)
```
--------------------------------
### Unity Cup Opponent Selection Logic
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Visualizes the decision flow for selecting an opponent in the Unity Cup. The bot analyzes double circle icons to determine favorable matchups.
```mermaid
stateDiagram-v2
[*] --> TapOpponent1
TapOpponent1 --> AnalyzePredictions1: Confirmation dialog opens
AnalyzePredictions1 --> RaceConfirmed: â
AnalyzePredictions1 --> TapOpponent2: < 3 double circles
TapOpponent2 --> AnalyzePredictions2: Confirmation dialog opens
AnalyzePredictions2 --> RaceConfirmed: â
AnalyzePredictions2 --> TapOpponent3: < 3 double circles
TapOpponent3 --> AnalyzePredictions3: Confirmation dialog opens
AnalyzePredictions3 --> RaceConfirmed: â
AnalyzePredictions3 --> ForceFallback: < 3 double circles
ForceFallback --> RaceConfirmed: Force select Opponent 2
RaceConfirmed --> [*]
```
--------------------------------
### Initialize Tooltip Element
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Creates and appends a hidden tooltip div to the document body. This element will be used to display custom tooltips.
```javascript
// ============================================== // Tooltip UI // ============================================== const tooltip = document.createElement("div") tooltip.id = "tooltip" tooltip.className = "custom-tooltip" document.body.appendChild(tooltip)
```
--------------------------------
### Switch View Tabs
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Manages tab switching between 'logs', 'images', and 'logFiles' views. It updates button classes and view visibility. It also includes logic for lazy-loading log files and scrolling to the bottom for the logs view.
```javascript
function switchTab(tabID) {
// Update buttons.
document.getElementById("logsTabBtn").classList.toggle("active", tabID === "logs")
document.getElementById("imagesTabBtn").classList.toggle("active", tabID === "images")
document.getElementById("logFilesTabBtn").classList.toggle("active", tabID === "logFiles")
// Update views.
document.getElementById("logsView").classList.toggle("active", tabID === "logs")
document.getElementById("imagesView").classList.toggle("active", tabID === "images")
document.getElementById("logFilesView").classList.toggle("active", tabID === "logFiles")
if (tabID === "logs" && autoScroll) {
scrollToBottom()
}
// Lazy-load the log files index on first activation; user can hit Refresh for re-fetches.
if (tabID === "logFiles" && !window.__logFilesLoaded) {
loadLogFiles()
window.__logFilesLoaded = true
}
}
```
--------------------------------
### Open Log File Function
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Fetches and displays the content of a log file in a modal. Handles large files by prompting download instead of inline preview. Includes error handling for fetch requests.
```javascript
async function openLogFile(name) {
const downloadEl = document.getElementById("logFileModalDownloadLink")
const bodyEl = document.getElementById("logFileModalBody")
const modal = document.getElementById("logFileModal")
downloadEl.href = `/logs/files/${encodeURIComponent(name)}?download=1`
downloadEl.setAttribute("download", name)
bodyEl.textContent = "Loading..."
modal.classList.add("visible")
const cached = logFilesCache.find((f) => f.name === name)
if (cached && cached.size > LOG_FILE_PREVIEW_BYTE_LIMIT) {
bodyEl.textContent = `File is ${formatFileSize(cached.size)} - too large to preview inline. Use the Download button above to save it.`
return
}
try {
const res = await fetch(`/logs/files/${encodeURIComponent(name)}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
bodyEl.textContent = await res.text()
} catch (err) {
bodyEl.textContent = `Failed to load log file: ${err.message}`
console.error("openLogFile failed:", err)
}
}
```
--------------------------------
### Solver Architecture Diagram
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Visual representation of the solver's components and their data flow, including settings, bridge, integration, solvers, and bot interaction.
```mermaid
flowchart LR
Settings["SmartRaceSolverSettings (TS)"] -->|SolverConfigSnapshot| Bridge["SmartRaceSolverModule (RN bridge)"]
Bridge --> Integration["SmartRaceSolverIntegration"]
Integration --> Solver["SmartRaceSolver.solve()"]
Solver -->|exact| MILP["MilpSolver (ojAlgo)"]
Solver -.->|fallback| Heuristic["Heuristic (beam search)"]
Integration --> History["RaceHistory + EpithetTracker"]
Bot["Racing.kt / Trackblazer.kt"] -->|peek / mark / commit| Integration
Integration --> LogStream["LogStreamServer (Race History calendar)"]
```
--------------------------------
### Revert Phone Resolution to Original
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Use these commands within the aShell You app to reset the device resolution and density to their original settings.
```bash
wm size reset && wm density reset
```
--------------------------------
### Render Single Log File Row
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Builds an HTML element for a single log file row, including name, timestamp, size, and a download link. Attaches a click listener to open the file inline.
```javascript
function renderLogFileRow(file) {
const row = document.createElement("div")
row.className = "log-file-row"
row.title = "Click to view"
const name = document.createElement("span")
name.className = "log-file-name"
name.textContent = file.name
const modified = document.createElement("span")
modified.className = "log-file-meta"
modified.textContent = formatLogFileTimestamp(file.modified)
const size = document.createElement("span")
size.className = "log-file-meta"
size.textContent = formatFileSize(file.size)
const download = document.createElement("a")
download.className = "log-file-download"
download.href = `/logs/files/${encodeURIComponent(file.name)}?download=1`
download.setAttribute("download", file.name)
download.textContent = "Download"
download.addEventListener("click", (e) => e.stopPropagation())
row.appendChild(name)
row.appendChild(modified)
row.appendChild(size)
row.appendChild(download)
row.addEventListener("click", () => openLogFile(file.name))
return row
}
```
--------------------------------
### Change Phone Resolution to 1080p
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Use these commands within the aShell You app to set the device resolution to 1080p. This method is faster and more accurate but only works when downscaling.
```bash
wm size 1080x1920 && wm density 240
```
--------------------------------
### Show Energy Recovery Tooltip
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Displays a tooltip showing the energy recovery breakdown by type. It dynamically generates HTML based on the energyBreakdown object.
```javascript
function showEnergyTooltip(event) { let content = '' const types = ["Rest", "Date", "Summer"] types.forEach(type => { const count = energyBreakdown[type] || 0 content += `
${type} ${count}
` }) content += "
" showTooltip(event, "Energy Recovery Breakdown", content) }
```
--------------------------------
### Race History Calendar Rendering Logic
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Contains logic for rendering the Race History calendar from a snapshot provided by the bot. It manages the latest calendar snapshot and results indexed by turn number for efficient hover-tooltips.
```javascript
// Latest snapshot kept for the hover-tooltip lookup. Decisions and results are
// indexed by turn number so showRaceCellTooltip is O(1) per hover.
var latestCalendarSnapshot = null
var latestResultsByTurn = {}
/**
* Renders the Race History calendar from a snapshot pushed by the bot.
* Snapshot shape: { currentTurn:int, decisions:{turn->{type,raceKey?,name?,grade?,raceTrack?,terrain?,distanceType?,distanceMeter
```
--------------------------------
### Commit Pending Race in Racing.kt
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Detects the first-place screen and commits the pending race. The plan is locked in on a win, and replanned on a loss to account for dead epithets.
```kotlin
commitPendingRace(won = firstPlace)
```
--------------------------------
### Handle Trainee Updates
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Handles updates to trainee stats and aptitudes using the pre-parsed 'trainee' object from the log data. It processes category and data fields to update the trainee's information.
```javascript
function handleTraineeUpdates(parsed, stateTarget) {
const trainee = parsed.trainee
if (!trainee) return
const category = trainee.category
const data = trainee.data
if (stateTarget) {
if (category === "Name") {
stateTarget.name = data.tri
```
--------------------------------
### Format TypeScript/TSX Files
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Run this command to format only TypeScript and TSX files using Prettier.
```bash
yarn format:tsx
```
--------------------------------
### UMA Android Automation Architecture
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
This diagram illustrates the core components and relationships within the UMA Android automation app's architecture, showing how different campaign types and systems interact.
```mermaid
classDiagram
class Campaign {
+process() TaskResult?
+handleMainScreen() Boolean
+decideNextAction() MainScreenAction
+executeAction() Boolean
}
Campaign <|-- UraFinale
Campaign <|-- UnityCup
Campaign <|-- Trackblazer
Campaign *-- Racing
Campaign *-- Training
Campaign *-- TrainingEvent
Campaign *-- SkillPlan
Campaign *-- Trainee
```
--------------------------------
### YOLOv8 Nano Model for Stat Detection
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Configuration details for using a YOLOv8 nano model for detecting stat gain digits. This method is an alternative to template matching and requires specific ONNX Runtime settings.
```text
When `enableYoloStatDetection` is enabled, stat gain digits are detected using a YOLOv8 nano model (`best.onnx`) instead of template matching. The model is trained to detect 11 classes (digits 0–9 and the '+' symbol) in small 130x50 pixel crop regions for each stat. It runs via ONNX Runtime with a confidence threshold of 0.8 and IoU threshold of 0.45 for NMS. The `YoloDetector` is loaded once as a singleton and kept in memory. Both detection methods coexist — the setting controls which one is used at runtime. The YOLO training pipeline and model export tools live in the [yolo/](yolo/) directory.
```
--------------------------------
### Training Event Detection Icon
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
This icon is used to detect the training event screen. Accurate detection is the first step for the bot to process and decide on the rewards offered during training events.
```Java
IconTrainingEventHorseshoe
```
--------------------------------
### Copy All Logs to Clipboard
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Copies the content of all currently stored log messages to the clipboard. Provides visual feedback on the button.
```javascript
function copyAllLogs() {
var text = allMessages
.map(function (m) {
var line = ""
if (m.parsed.timestamp) line += m.parsed.timestamp + " "
if (m.parsed.level) line += "[" + m.parsed.level + "] "
line += m.parsed.message
return m.parsed.newline + line
})
.join("\n")
navigator.clipboard.writeText(text).then(function () {
// Briefly flash the button to confirm.
var btn = event.target.closest(".toolbar-btn")
if (btn) {
var original = btn.textContent
btn.textContent = "✓ Copied!"
setTimeout(function () {
btn.innerHTML = "📋 Copy"
}, 1500)
}
})
}
```
--------------------------------
### Beam Search Heuristic Backend
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
The beam search heuristic is used as a fallback when the MILP model is infeasible. It expands beams, scores children, and prunes to a default beam width.
```kotlin
DEFAULT_BEAM_WIDTH = 32
```
--------------------------------
### Render Race History Calendar
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Renders the race history calendar grid based on provided snapshot data. It handles empty states and populates the calendar with cells representing turns, decisions, and results.
```javascript
function renderCalendar(snapshot) {
snapshot = snapshot || { currentTurn: 1, decisions: {}, results: [] }
if (typeof snapshot !== "object")
return
latestCalendarSnapshot = snapshot
latestResultsByTurn = {}
var resultsForIndex = Array.isArray(snapshot.results) ? snapshot.results : []
for (var ri = 0; ri < resultsForIndex.length; ri++) {
var rr = resultsForIndex[ri]
if (rr && typeof rr.turn === "number")
latestResultsByTurn[rr.turn] = rr
}
var container = document.getElementById("raceHistoryYears")
var badgeEl = document.getElementById("raceHistoryTurnBadge")
if (!container || !badgeEl)
return
var hasRealSnapshot = typeof snapshot.currentTurn === "number" && (
Object.keys(snapshot.decisions || {}).length > 0 ||
(snapshot.results || []).length > 0
)
var currentTurn = typeof snapshot.currentTurn === "number" ? snapshot.currentTurn : 1
badgeEl.textContent = hasRealSnapshot ? ("Turn " + currentTurn + " / 72") : "Turn -"
// Index results by turn for O(1) lookup during cell rendering.
var resultsByTurn = {}
var results = Array.isArray(snapshot.results) ? snapshot.results : []
for (var i = 0; i < results.length; i++) {
var r = results[i]
if (r && typeof r.turn === "number")
resultsByTurn[r.turn] = r
}
var decisions = snapshot.decisions && typeof snapshot.decisions === "object" ? snapshot.decisions : {}
var html = ""
for (var y = 0; y < CAL_YEAR_LABELS.length; y++) {
var year = CAL_YEAR_LABELS[y]
html += ''
html += '
' + year.name + '
'
for (var row = 0; row < 6; row++) {
html += '
'
for (var col = 0; col < 4; col++) {
var offset = row * 4 + col
var turn = year.startTurn + offset
html += renderCalendarCell(turn, offset, decisions[String(turn)], resultsByTurn[turn])
}
html += '
'
}
html += '
'
}
container.innerHTML = html
}
```
--------------------------------
### Revert Phone Resolution Changes via ADB
Source: https://github.com/steve1316/uma-android-automation/blob/master/README.md
Execute these ADB commands from a computer to reset the device resolution and density to their original settings.
```bash
adb shell wm size reset
```
```bash
adb shell wm density reset
```
--------------------------------
### Handle Incoming Image Data
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Creates and appends an image card to the image grid for each incoming image data object. Updates the image count and status. Sets up an onclick handler to open the image in a modal.
```javascript
function handleIncomingImage(data) {
const grid = document.getElementById("imageGrid")
const card = document.createElement("div")
card.className = "image-card"
const img = document.createElement("img")
img.src = "data:image/jpeg;base64," + data.data
img.alt = data.name
const info = document.createElement("div")
info.className = "image-info"
info.textContent = data.name
card.appendChild(info)
card.appendChild(img)
card.onclick = function() {
openModal(img.src)
}
grid.appendChild(card)
receivedImagesCount++
document.getElementById("imageCount").textContent = receivedImagesCount
document.getElementById("imageStatus").textContent = "Receiving images..."
}
```
--------------------------------
### Show Simple Action Counter Tooltip
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Displays a tooltip for action counters, showing the total number of actions performed. It extracts the count from an element with the class 'stat-value'.
```javascript
/**
* Shows a simple title/count tooltip for other action counters.
*/
function showSimpleTooltip(event, title) {
const count = event.currentTarget.querySelector(".stat-value").textContent
const content = ` Total actions ${count}
`
showTooltip(event, title, content)
}
```
--------------------------------
### Show Race History Tooltip
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Displays a tooltip summarizing race history by grade. It lists counts for recognized grades (Finale, G1-G3, OP, etc.) and includes an 'Other' category for unrecognized grades.
```javascript
/**
* Shows a specific tooltip for the race action counter.
*/
function showRaceTooltip(event) {
let content = ''
const grades = ["Finale", "G1", "G2", "G3", "OP", "Pre-OP", "Maiden", "Mandatory", "Standalone"]
let found = false
grades.forEach(grade => {
const count = raceBreakdown[grade] || 0
if (count > 0) {
found = true
content += `
${grade} ${count}
`
}
})
// Show "Other" if there are any unrecognized grades.
let otherCount = 0
for (const [grade, count] of Object.entries(raceBreakdown)) {
if (!grades.includes(grade) && count > 0) {
otherCount += count
}
}
if (otherCount > 0) {
found = true
content += `
Other ${otherCount}
`
}
// If no races found, display a message
if (!found) {
content += `
No races recorded
`
}
content += "
"
showTooltip(event, "Race History", content)
}
```
--------------------------------
### Trackblazer Scenario Decision Flow
Source: https://github.com/steve1316/uma-android-automation/blob/master/HOW_IT_WORKS.md
Illustrates the custom decision-making process for the Trackblazer scenario, including checks for season, energy, and irregular training.
```mermaid
flowchart TD
Start["Trackblazer
decideNextAction()"] --> Summer{"Is it
Summer?"}
Summer -->|Yes| TRAIN["â TRAIN
(Summer training)"]
Summer -->|No| Finale{"Finale turns
73-75?"}
Finale -->|Yes| TRAIN2["â TRAIN
(Finale training)"]
Finale -->|No| EnergyGuard{"Energy
3+ consecutive races?"}
EnergyGuard -->|Yes| REST["â REST
(avoid -30 stat penalty)"]
EnergyGuard -->|No| Irregular{"Irregular Training
enabled + not checked?"}
Irregular -->|Yes| EvalTraining["Open training screen
Analyze all 5 trainings"]
EvalTraining --> ValidFound{"High-value training
found?"}
ValidFound -->|Yes| TRAIN3["â TRAIN
(irregular training)"]
ValidFound -->|No| BackOut["Close training screen
Mark as checked"]
BackOut --> BaseDecision["super.decideNextAction()
(base priority waterfall)"]
Irregular -->|No| BaseDecision
```
--------------------------------
### Handle Energy and Mood Updates
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Updates the energy and mood state based on `energyInfo` from parsed data. Can update a target state object or the dashboard DOM.
```javascript
function handleEnergyInfo(parsed, stateTarget) { const info = parsed.energyInfo if (!info) return if (stateTarget) { stateTarget.energy = info.to + "%" if (info.moodTo) stateTarget.mood = info.moodTo } else { document.getElementById("dashboardEnergy").textContent = info.to + "%" if (info.moodTo) updateMoodBadge(info.moodTo) } }
```
--------------------------------
### Show Custom Tooltip
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Displays a custom tooltip with a title and content at the event's current target. The tooltip's visibility and position are updated.
```javascript
/**
* Shows the custom tooltip with the given content.
*/
function showTooltip(event, title, content) {
const target = event.currentTarget
let html = `${title}
`
html += `${content}
`
tooltip.innerHTML = html
tooltip.classList.add("visible")
updateTooltipPosition(event)
}
```
--------------------------------
### Open Image Enlargement Modal
Source: https://github.com/steve1316/uma-android-automation/blob/master/android/app/src/main/assets/log_viewer.html
Opens a modal to display an image in a larger size. Requires an HTML element with id 'imageModal' and 'enlargedImage'.
```javascript
function openModal(src) {
const modal = document.getElementById("imageModal")
const modalImg = document.getElementById("enlargedImage")
modal.classList.add("visible")
modalImg.src = src
}
```