{"owner":"Fate-Grand-Automata","repo":"FGA","hasSkills":true,"totalSkillsCount":17,"totalTokensCount":5647,"categories":["anthropic-skill","copilot-instructions","root-instruction","subagent-persona","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/extract-template-asset/SKILL.md",".claude/rules/github-workflows.md",".claude/rules/automation-scripts.md",".github/copilot-instructions.md",".claude/rules/coordinates.md",".claude/rules/dependencies.md","AGENTS.md",".claude/rules/translations.md",".claude/rules/servers-and-assets.md","docs/agents/automation-scripts.md","docs/agents/README.md","docs/agents/coordinates.md","docs/agents/dependencies.md","docs/agents/github-workflows.md","docs/agents/servers-and-assets.md","docs/agents/translations.md",".claude/skills/extract-template-asset/crop_template.py"],"skills":{".claude/skills/extract-template-asset/SKILL.md":"---\nname: extract-template-asset\ndescription: Cut a 720p template image for app/src/main/assets out of a game screenshot, using a Region from scripts/.../locations. Use when adding or fixing a template for a server (En, Jp, Cn, Tw, Kr) - \"add a Kr asset\", \"the image isn't detected on TW\", \"make a template for X\" - or when checking that an existing Region actually covers what it should.\n---\n\n# Extracting a template asset from a screenshot\n\nRead [`docs/agents/coordinates.md`](../../../docs/agents/coordinates.md) first for the two\ncoordinate systems. This skill is the mechanical procedure on top of it.\n\n`crop_template.py` (next to this file, needs Pillow) reproduces `FgoGameAreaManager` and\n`RealScale`, so a `Region` copied out of `locations/*.kt` resolves to the pixels the script\nactually searches. Do the arithmetic with it rather than by hand - the game-area rules are\nwhere this goes wrong.\n\n## Before cropping\n\n1. **Find the Region** the script searches, in `scripts/.../locations/*.kt`. Note the\n   `xFromRight()` / `xFromCenter()` / `yFromBottom()` suffix and whether it sits behind an\n   `if (isWide)` branch - screenshots wider than 18:9 usually take a different Region.\n2. **Find the caller** in `scripts/.../modules/`. Two things change what you cut:\n   - Matching is grayscale unless the `find` is inside `useColor { }`.\n   - Some callers click relative to the *match*, not a fixed location -\n     `Caster.openCommandSpellMenu` uses `Location(match.region.x, 150)`, so the template's\n     left edge decides where the tap lands.\n3. **Get the screenshot** from `docs/reference-images/<Server>/`, and put any new one there\n   so the template can be re-cut later - see\n   [`docs/agents/servers-and-assets.md`](../../../docs/agents/servers-and-assets.md).\n   Those are stored at 720p; commit a contributed screenshot downscaled, not raw. The script\n   downscales whatever it is given, so it works either way.\n4. **Look at the existing assets** for the same `Images` entry. Match their convention;\n   they are usually a tight crop of the localized label, not the icon. `En` is the\n   fallback, so only add a server file when the art genuinely differs.\n\n## Cropping\n\nSource screenshots live in `docs/reference-images/<Server>/`. Stage 1 - see what the region\ncovers:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right --out-dir /tmp/cs\n```\n\nThis writes `*-region.png` (the search region at 720p) and `*-region-grid.png` (the same\nthing upscaled with a labelled 5px grid). If the region does not sit on the target, the\nRegion is wrong or you picked the wrong `isWide` branch - fix that before going further.\n\nStage 2 - read the template box off the grid image and cut it:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right \\\n    --template 48,31,21,11 --click-y 150 \\\n    --out app/src/main/assets/Kr/command_spell.png\n```\n\n`--template` is in pixels relative to the region crop's top-left. `--click-y` marks where a\nmatch-anchored click would land on the verification image.\n\n## Choosing the box\n\n**Only include pixels that are part of the UI.** The battle scene behind a HUD element\nchanges every quest - the same element sits on a night sky on one server's screenshot and\non green foliage on another. Any background you include is noise that will not match. This\nis why the existing templates are small and sit inside glyph strokes.\n\nJudge coverage by eye on the grid image. Automated \"how much of this box is glyph\" scoring\nis unreliable, because dark scenery reads the same as a dark outline.\n\nPrefer a box that is entirely inside dense artwork - the filled part of a character, a\nsolid bar - over a larger box that reaches into the scene at its corners.\n\n## Checking the result\n\nOpen `*-verify.png`. The yellow box is the search region, green is the template, red is the\nclick. Confirm the region sits on the target, the template is all UI, and the click lands on\nsomething tappable. Also confirm the written file is 720p-scale: a template cut this way is\nhalf the Region's script dimensions, so `Region(..., 190, 120)` can hold at most 95x60.",".claude/rules/github-workflows.md":"---\npaths: \".github/workflows/**\"\ndescription: Workflow changes must regenerate the actions lockfile.\n---\n\nEvery workflow change must be committed with a regenerated `.github/workflows/actions.lock`\n(`gh actions-lock`). Otherwise its runs are blocked by the repository's *Require lockfile* policy.\n\nRead [`docs/agents/github-workflows.md`](../../docs/agents/github-workflows.md).\n",".claude/rules/automation-scripts.md":"---\npaths: \"scripts/**,app/src/main/java/io/github/fate_grand_automata/di/**,app/src/main/java/io/github/fate_grand_automata/runner/**\"\ndescription: How a script run is wired through Hilt, and the patterns automation logic follows.\n---\n\nEvery script run gets its own Hilt component, and `script()` never returns normally — it exits\nby exception.\n\nRead [`docs/agents/automation-scripts.md`](../../docs/agents/automation-scripts.md).\n",".github/copilot-instructions.md":"# Copilot instructions\n\nThe repo-wide guidance for this project lives in [`AGENTS.md`](../AGENTS.md) — read it first.\nPath-specific guidance lives in [`docs/agents/`](../docs/agents/); the files in\n[`.github/instructions/`](instructions/) point at it with an `applyTo` glob.\n",".claude/rules/coordinates.md":"---\npaths: \"scripts/src/main/java/**/locations/*.kt,app/src/main/assets/**,libautomata/**\"\ndescription: The 720p/1440p coordinate systems used by image matching and Location/Region values.\n---\n\nImage matching runs at 720p, coordinates are expressed at 1440p, and the X origin depends on the\ndevice aspect ratio — guessing produces values that are wrong on most devices.\n\nRead [`docs/agents/coordinates.md`](../../docs/agents/coordinates.md).\n",".claude/rules/dependencies.md":"---\npaths: \"gradle/libs.versions.toml,gradle/wrapper/**,**/build.gradle.kts,settings.gradle.kts,Gemfile,Gemfile.lock,.github/workflows/**\"\ndescription: Toolchain pins (JDK/bytecode/AGP/compileSdk) and the order dependency bumps must follow.\n---\n\nThe AGP, Gradle, Kotlin and androidx bumps are interlocked and only succeed in one order; the\nJDK, bytecode-target and `compileSdk` pins are deliberate.\n\nRead [`docs/agents/dependencies.md`](../../docs/agents/dependencies.md).\n","AGENTS.md":"# AGENTS.md\n\nPath-specific guidance lives in [`docs/agents/`](docs/agents) so it is loaded only when relevant: read a guide when its trigger below matches. Copilot and Claude Code pick the same guides up automatically via stubs in `.github/instructions/` and `.claude/rules/` — see [`docs/agents/README.md`](docs/agents/README.md).\n\n## What this is\n\nFate/Grand Automata (FGA) — an Android app that automates farming in the game *Fate/Grand Order*. It does **not** touch the game process: it reads the screen via MediaProjection, matches templates with OpenCV (plus Tesseract OCR), and taps/swipes through an AccessibilityService. Android 7+ (minSdk 24), no root required (root screenshots are an optional path).\n\n## Topic guides — read before touching these paths\n\n- **Any on-screen coordinate, any file in `scripts/.../locations/`, any template image added to `app/src/main/assets/`** — [`docs/agents/coordinates.md`](docs/agents/coordinates.md). Matching runs at 720p, coordinates are 1440p, and the X origin depends on aspect ratio.\n- **`gradle/libs.versions.toml`, the Gradle wrapper, any `build.gradle.kts`** — [`docs/agents/dependencies.md`](docs/agents/dependencies.md). The AGP/Gradle/Kotlin/androidx bumps only succeed in one order; it also holds the JDK, bytecode-target and `compileSdk` pins.\n- **Automation logic in `scripts/`, or the Hilt wiring in `app/di/` and `app/runner/`** — [`docs/agents/automation-scripts.md`](docs/agents/automation-scripts.md). Every run gets its own Hilt component, and `script()` never returns normally.\n- **Any file in `app/src/main/assets/`** — [`docs/agents/servers-and-assets.md`](docs/agents/servers-and-assets.md). Template images are per-server with an `En` fallback; server choice and app language are independent.\n- **Strings under `app/src/main/res/`** — [`docs/agents/translations.md`](docs/agents/translations.md). English strings live in `values/localized.xml`; the translated `values-*` copies are synced from POEditor.\n- **Any file in `.github/workflows/`** — [`docs/agents/github-workflows.md`](docs/agents/github-workflows.md). Every workflow change must regenerate `actions.lock` with `gh actions-lock`.\n\n## Build & test\n\n```bash\n./gradlew :app:compileDebugKotlin # Compiles the kotlin code for the android app.\n./gradlew :scripts:test          # the only unit tests in the repo (JUnit 5 + assertk + mockk)\n./gradlew :scripts:test --tests '*SupportSelectionTest'          # single test class\n./gradlew :scripts:test --tests '*SupportSelectionTest.someName' # single test method\n./gradlew lint                   # abortOnError = false, so lint never fails the build\n./gradlew dependencyUpdates      # ben-manes versions plugin\n```\n\nEverything builds on **JDK 21** via a Gradle toolchain pin; emitted bytecode stays at **Java 11**.\n\n## Module layout and dependency direction\n\n```\napp  ──▶ scripts ──▶ libautomata\n └──▶ prefs ──▶ scripts\n```\n\n- **`libautomata/`** — pure JVM Kotlin (no Android deps). Game-agnostic automation primitives: `Region`, `Location`, `Size`, `Pattern`, `AutomataApi`/`StandardAutomataApi`, `ScreenshotManager`, `ImageMatcher`, `Clicker`, `Swiper`, `Waiter`, `Scale`, `GameAreaManager`, `EntryPoint`, `ExitManager`. Everything platform-specific is an interface (`ScreenshotService`, `GestureService`, `OcrService`, `PlatformImpl`, `PlatformPrefs`) implemented in `app`.\n- **`scripts/`** — pure JVM Kotlin. All FGO knowledge: entry points, per-screen modules, coordinates, image names, prefs *interfaces*. This is where automation logic belongs, and the only module with tests (it's testable precisely because it has no Android dependency — keep it that way).\n- **`prefs/`** — Android library implementing the `I*Preferences` interfaces from `scripts` on top of `flow-preferences`. `PrefsCore` holds the raw `Pref<T>` delegates; `PreferencesImpl` adapts them to the script-facing interfaces.\n- **`app/`** — Compose UI, Hilt wiring, the two services, and the Android implementations of the `libautomata` interfaces (`imaging/`, `accessibility/`, `root/`, `util/`).\n\n## Repo conventions\n\n- Kotlin official code style; `kotlin.time.Duration` is used everywhere for waits.\n- Hilt + KSP for DI; Compose (BOM-managed) with several `ExperimentalX` opt-ins already enabled in `app/build.gradle.kts`.\n- Dependencies live in `gradle/libs.versions.toml` — always add/bump there, never inline coordinates.\n- `wiki/` is the source of the GitHub wiki (published by `.github/workflows/publish-wiki.yml`) — user-facing docs go there, not in the README.\n- Agent guidance: keep this file to what's true repo-wide and put path-specific rules in [`docs/agents/`](docs/agents), following [`docs/agents/README.md`](docs/agents/README.md).\n",".claude/rules/translations.md":"---\npaths: \"app/src/main/res/**,app/src/main/assets/Support/**\"\ndescription: The English string source and the external POEditor translation flow.\n---\n\nEnglish strings live in `values/localized.xml` and are edited here; the translated\n`values-*` copies come from POEditor.\n\nRead [`docs/agents/translations.md`](../../docs/agents/translations.md).\n",".claude/rules/servers-and-assets.md":"---\npaths: \"app/src/main/assets/**\"\ndescription: Per-server template image folders and the En fallback.\n---\n\nTemplate images are per-server with an `En` fallback and must be 720p.\n\nRead [`docs/agents/servers-and-assets.md`](../../docs/agents/servers-and-assets.md).\n","docs/agents/automation-scripts.md":"# Writing automation logic\n\n## How a script run is wired (Hilt)\n\nTwo long-lived services: `ScriptRunnerService` (foreground service owning the overlay,\nnotification, and `ScriptManager`) and `TapperService` (AccessibilityService providing\ngestures).\n\nEach *run* gets its own Hilt component: `ScriptComponent` (`@ScriptScope`, child of\n`ServiceComponent`, built via `ScriptComponentBuilder` with the live `ScreenshotService`\nbound in). `ScriptManager` builds it, pulls the chosen `EntryPoint` out through the\n`ScriptEntryPoint` Hilt entry point, runs it on its own thread, and tears the component\ndown at exit (including explicitly closing the Tesseract `OcrService`). Consequences:\n\n- `ScriptScope` singletons (`Locations`, `ScriptAreaTransforms`, `ScreenshotManager`,\n  `FgoAutomataApi`, …) are recreated per run, so screen-geometry values are computed once\n  per run and cached.\n- Adding a new script mode means: entry point class in `scripts/.../entrypoints/`, a getter\n  on `ScriptEntryPoint`, a `ScriptModeEnum` value, and a branch in\n  `ScriptManager.getEntryPoint`.\n- Bindings for a run live in `app/di/script/` (`LibAutomataModule` for `libautomata`\n  interfaces, `ScriptsModule` for FGO-level ones).\n\n## Script structure\n\nEntry points extend `EntryPoint` and implement `script(): Nothing`. `ExitManager` implements\nstop/pause by throwing `ScriptAbortException` from inside wait/click primitives, which is why\n`script()` never returns normally — exit and errors surface through `scriptExitListener`.\n\nThe dominant pattern (see `AutoBattle.loop()`) is a **map of screen detectors to\nhandlers**, evaluated in order inside `useSameSnapIn { }`, one action per iteration, then a\nshort wait. Order matters: earlier entries win. Detectors must be cheap image/color checks,\nnot stateful logic.\n\nMix in `IFgoAutomataApi` (delegating to an injected `FgoAutomataApi`) to get `prefs`,\n`images`, `locations`, `messages`, plus the `Region`/`Location` extension DSL:\n`images[Images.Menu] in someRegion`, `region.exists(pattern, timeout)`,\n`region.waitVanish(...)`, `location.click()`, `duration.wait()`.\n\n- **`useSameSnapIn { }`** caches one screenshot for the whole block. Any group of checks\n  that must agree on a single frame has to be inside it; without it every `exists` takes a\n  fresh screenshot (slow, and racy across animations).\n- **User-facing text** never goes in `scripts/` (no Android `R`). Emit a\n  `ScriptNotify`/`ScriptLog` through `messages: IScriptMessages`;\n  `app/util/ScriptMessages.kt` maps it to a localized string, toast, or notification.\n- **Never hardcode wait/similarity magic numbers casually** — `PlatformPrefs` exposes\n  user-tunable `minSimilarity`, `waitMultiplier`, `swipeMultiplier` (the Fine-Tune screen).\n  Explicit `similarity` arguments in `Region.exists` should be rare; anything under ~0.65\n  means the template image is wrong.\n","docs/agents/README.md":"# Agent topic guides\n\nThe path-specific half of this repo's AI-agent guidance; the always-loaded half is\n[`AGENTS.md`](../../AGENTS.md) at the repo root. These files are the single source of\ntruth — keep them provider-neutral (plain prose, no frontmatter, no tool names, no\n`@`-imports) so any agent can read them, and never copy their prose elsewhere.\n\nThree layers point at them, so an agent only pulls in a topic when it is relevant:\n\n| Layer | Files | Mechanism |\n| --- | --- | --- |\n| Universal | `AGENTS.md` router section | Every agent reads the root file; each entry names a trigger and the guide to read. |\n| GitHub Copilot | `.github/instructions/*.instructions.md` | `applyTo:` glob, applied when a matching file is in context. |\n| Claude Code | `.claude/rules/*.md` | `paths:` glob, loaded when the model touches a matching file. |\n\nThe vendor files are stubs: a glob, a one-line reason, a pointer here. Adding a guide means\na stub in each vendor directory and a trigger line in the root router; adding an agent\n(Cursor's `.cursor/rules/*.mdc`, Windsurf's `.windsurf/rules/`) means copying a stub and\nrenaming the glob key.\n","docs/agents/coordinates.md":"# Subagent: coordinates\n\nPath: `docs/agents/coordinates.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/coordinates.md)","docs/agents/dependencies.md":"# Subagent: dependencies\n\nPath: `docs/agents/dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/dependencies.md)","docs/agents/github-workflows.md":"# Subagent: github-workflows\n\nPath: `docs/agents/github-workflows.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/github-workflows.md)","docs/agents/servers-and-assets.md":"# Subagent: servers-and-assets\n\nPath: `docs/agents/servers-and-assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/servers-and-assets.md)","docs/agents/translations.md":"# Subagent: translations\n\nPath: `docs/agents/translations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/translations.md)",".claude/skills/extract-template-asset/crop_template.py":"# Claude Directive: crop_template.py\n\nPath: `.claude/skills/extract-template-asset/crop_template.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/skills/extract-template-asset/crop_template.py)"},"files":{".claude/skills/extract-template-asset/SKILL.md":"---\nname: extract-template-asset\ndescription: Cut a 720p template image for app/src/main/assets out of a game screenshot, using a Region from scripts/.../locations. Use when adding or fixing a template for a server (En, Jp, Cn, Tw, Kr) - \"add a Kr asset\", \"the image isn't detected on TW\", \"make a template for X\" - or when checking that an existing Region actually covers what it should.\n---\n\n# Extracting a template asset from a screenshot\n\nRead [`docs/agents/coordinates.md`](../../../docs/agents/coordinates.md) first for the two\ncoordinate systems. This skill is the mechanical procedure on top of it.\n\n`crop_template.py` (next to this file, needs Pillow) reproduces `FgoGameAreaManager` and\n`RealScale`, so a `Region` copied out of `locations/*.kt` resolves to the pixels the script\nactually searches. Do the arithmetic with it rather than by hand - the game-area rules are\nwhere this goes wrong.\n\n## Before cropping\n\n1. **Find the Region** the script searches, in `scripts/.../locations/*.kt`. Note the\n   `xFromRight()` / `xFromCenter()` / `yFromBottom()` suffix and whether it sits behind an\n   `if (isWide)` branch - screenshots wider than 18:9 usually take a different Region.\n2. **Find the caller** in `scripts/.../modules/`. Two things change what you cut:\n   - Matching is grayscale unless the `find` is inside `useColor { }`.\n   - Some callers click relative to the *match*, not a fixed location -\n     `Caster.openCommandSpellMenu` uses `Location(match.region.x, 150)`, so the template's\n     left edge decides where the tap lands.\n3. **Get the screenshot** from `docs/reference-images/<Server>/`, and put any new one there\n   so the template can be re-cut later - see\n   [`docs/agents/servers-and-assets.md`](../../../docs/agents/servers-and-assets.md).\n   Those are stored at 720p; commit a contributed screenshot downscaled, not raw. The script\n   downscales whatever it is given, so it works either way.\n4. **Look at the existing assets** for the same `Images` entry. Match their convention;\n   they are usually a tight crop of the localized label, not the icon. `En` is the\n   fallback, so only add a server file when the art genuinely differs.\n\n## Cropping\n\nSource screenshots live in `docs/reference-images/<Server>/`. Stage 1 - see what the region\ncovers:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right --out-dir /tmp/cs\n```\n\nThis writes `*-region.png` (the search region at 720p) and `*-region-grid.png` (the same\nthing upscaled with a labelled 5px grid). If the region does not sit on the target, the\nRegion is wrong or you picked the wrong `isWide` branch - fix that before going further.\n\nStage 2 - read the template box off the grid image and cut it:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right \\\n    --template 48,31,21,11 --click-y 150 \\\n    --out app/src/main/assets/Kr/command_spell.png\n```\n\n`--template` is in pixels relative to the region crop's top-left. `--click-y` marks where a\nmatch-anchored click would land on the verification image.\n\n## Choosing the box\n\n**Only include pixels that are part of the UI.** The battle scene behind a HUD element\nchanges every quest - the same element sits on a night sky on one server's screenshot and\non green foliage on another. Any background you include is noise that will not match. This\nis why the existing templates are small and sit inside glyph strokes.\n\nJudge coverage by eye on the grid image. Automated \"how much of this box is glyph\" scoring\nis unreliable, because dark scenery reads the same as a dark outline.\n\nPrefer a box that is entirely inside dense artwork - the filled part of a character, a\nsolid bar - over a larger box that reaches into the scene at its corners.\n\n## Checking the result\n\nOpen `*-verify.png`. The yellow box is the search region, green is the template, red is the\nclick. Confirm the region sits on the target, the template is all UI, and the click lands on\nsomething tappable. Also confirm the written file is 720p-scale: a template cut this way is\nhalf the Region's script dimensions, so `Region(..., 190, 120)` can hold at most 95x60.",".claude/rules/github-workflows.md":"---\npaths: \".github/workflows/**\"\ndescription: Workflow changes must regenerate the actions lockfile.\n---\n\nEvery workflow change must be committed with a regenerated `.github/workflows/actions.lock`\n(`gh actions-lock`). Otherwise its runs are blocked by the repository's *Require lockfile* policy.\n\nRead [`docs/agents/github-workflows.md`](../../docs/agents/github-workflows.md).\n",".claude/rules/automation-scripts.md":"---\npaths: \"scripts/**,app/src/main/java/io/github/fate_grand_automata/di/**,app/src/main/java/io/github/fate_grand_automata/runner/**\"\ndescription: How a script run is wired through Hilt, and the patterns automation logic follows.\n---\n\nEvery script run gets its own Hilt component, and `script()` never returns normally — it exits\nby exception.\n\nRead [`docs/agents/automation-scripts.md`](../../docs/agents/automation-scripts.md).\n",".github/copilot-instructions.md":"# Copilot instructions\n\nThe repo-wide guidance for this project lives in [`AGENTS.md`](../AGENTS.md) — read it first.\nPath-specific guidance lives in [`docs/agents/`](../docs/agents/); the files in\n[`.github/instructions/`](instructions/) point at it with an `applyTo` glob.\n",".claude/rules/coordinates.md":"---\npaths: \"scripts/src/main/java/**/locations/*.kt,app/src/main/assets/**,libautomata/**\"\ndescription: The 720p/1440p coordinate systems used by image matching and Location/Region values.\n---\n\nImage matching runs at 720p, coordinates are expressed at 1440p, and the X origin depends on the\ndevice aspect ratio — guessing produces values that are wrong on most devices.\n\nRead [`docs/agents/coordinates.md`](../../docs/agents/coordinates.md).\n",".claude/rules/dependencies.md":"---\npaths: \"gradle/libs.versions.toml,gradle/wrapper/**,**/build.gradle.kts,settings.gradle.kts,Gemfile,Gemfile.lock,.github/workflows/**\"\ndescription: Toolchain pins (JDK/bytecode/AGP/compileSdk) and the order dependency bumps must follow.\n---\n\nThe AGP, Gradle, Kotlin and androidx bumps are interlocked and only succeed in one order; the\nJDK, bytecode-target and `compileSdk` pins are deliberate.\n\nRead [`docs/agents/dependencies.md`](../../docs/agents/dependencies.md).\n","AGENTS.md":"# AGENTS.md\n\nPath-specific guidance lives in [`docs/agents/`](docs/agents) so it is loaded only when relevant: read a guide when its trigger below matches. Copilot and Claude Code pick the same guides up automatically via stubs in `.github/instructions/` and `.claude/rules/` — see [`docs/agents/README.md`](docs/agents/README.md).\n\n## What this is\n\nFate/Grand Automata (FGA) — an Android app that automates farming in the game *Fate/Grand Order*. It does **not** touch the game process: it reads the screen via MediaProjection, matches templates with OpenCV (plus Tesseract OCR), and taps/swipes through an AccessibilityService. Android 7+ (minSdk 24), no root required (root screenshots are an optional path).\n\n## Topic guides — read before touching these paths\n\n- **Any on-screen coordinate, any file in `scripts/.../locations/`, any template image added to `app/src/main/assets/`** — [`docs/agents/coordinates.md`](docs/agents/coordinates.md). Matching runs at 720p, coordinates are 1440p, and the X origin depends on aspect ratio.\n- **`gradle/libs.versions.toml`, the Gradle wrapper, any `build.gradle.kts`** — [`docs/agents/dependencies.md`](docs/agents/dependencies.md). The AGP/Gradle/Kotlin/androidx bumps only succeed in one order; it also holds the JDK, bytecode-target and `compileSdk` pins.\n- **Automation logic in `scripts/`, or the Hilt wiring in `app/di/` and `app/runner/`** — [`docs/agents/automation-scripts.md`](docs/agents/automation-scripts.md). Every run gets its own Hilt component, and `script()` never returns normally.\n- **Any file in `app/src/main/assets/`** — [`docs/agents/servers-and-assets.md`](docs/agents/servers-and-assets.md). Template images are per-server with an `En` fallback; server choice and app language are independent.\n- **Strings under `app/src/main/res/`** — [`docs/agents/translations.md`](docs/agents/translations.md). English strings live in `values/localized.xml`; the translated `values-*` copies are synced from POEditor.\n- **Any file in `.github/workflows/`** — [`docs/agents/github-workflows.md`](docs/agents/github-workflows.md). Every workflow change must regenerate `actions.lock` with `gh actions-lock`.\n\n## Build & test\n\n```bash\n./gradlew :app:compileDebugKotlin # Compiles the kotlin code for the android app.\n./gradlew :scripts:test          # the only unit tests in the repo (JUnit 5 + assertk + mockk)\n./gradlew :scripts:test --tests '*SupportSelectionTest'          # single test class\n./gradlew :scripts:test --tests '*SupportSelectionTest.someName' # single test method\n./gradlew lint                   # abortOnError = false, so lint never fails the build\n./gradlew dependencyUpdates      # ben-manes versions plugin\n```\n\nEverything builds on **JDK 21** via a Gradle toolchain pin; emitted bytecode stays at **Java 11**.\n\n## Module layout and dependency direction\n\n```\napp  ──▶ scripts ──▶ libautomata\n └──▶ prefs ──▶ scripts\n```\n\n- **`libautomata/`** — pure JVM Kotlin (no Android deps). Game-agnostic automation primitives: `Region`, `Location`, `Size`, `Pattern`, `AutomataApi`/`StandardAutomataApi`, `ScreenshotManager`, `ImageMatcher`, `Clicker`, `Swiper`, `Waiter`, `Scale`, `GameAreaManager`, `EntryPoint`, `ExitManager`. Everything platform-specific is an interface (`ScreenshotService`, `GestureService`, `OcrService`, `PlatformImpl`, `PlatformPrefs`) implemented in `app`.\n- **`scripts/`** — pure JVM Kotlin. All FGO knowledge: entry points, per-screen modules, coordinates, image names, prefs *interfaces*. This is where automation logic belongs, and the only module with tests (it's testable precisely because it has no Android dependency — keep it that way).\n- **`prefs/`** — Android library implementing the `I*Preferences` interfaces from `scripts` on top of `flow-preferences`. `PrefsCore` holds the raw `Pref<T>` delegates; `PreferencesImpl` adapts them to the script-facing interfaces.\n- **`app/`** — Compose UI, Hilt wiring, the two services, and the Android implementations of the `libautomata` interfaces (`imaging/`, `accessibility/`, `root/`, `util/`).\n\n## Repo conventions\n\n- Kotlin official code style; `kotlin.time.Duration` is used everywhere for waits.\n- Hilt + KSP for DI; Compose (BOM-managed) with several `ExperimentalX` opt-ins already enabled in `app/build.gradle.kts`.\n- Dependencies live in `gradle/libs.versions.toml` — always add/bump there, never inline coordinates.\n- `wiki/` is the source of the GitHub wiki (published by `.github/workflows/publish-wiki.yml`) — user-facing docs go there, not in the README.\n- Agent guidance: keep this file to what's true repo-wide and put path-specific rules in [`docs/agents/`](docs/agents), following [`docs/agents/README.md`](docs/agents/README.md).\n",".claude/rules/translations.md":"---\npaths: \"app/src/main/res/**,app/src/main/assets/Support/**\"\ndescription: The English string source and the external POEditor translation flow.\n---\n\nEnglish strings live in `values/localized.xml` and are edited here; the translated\n`values-*` copies come from POEditor.\n\nRead [`docs/agents/translations.md`](../../docs/agents/translations.md).\n",".claude/rules/servers-and-assets.md":"---\npaths: \"app/src/main/assets/**\"\ndescription: Per-server template image folders and the En fallback.\n---\n\nTemplate images are per-server with an `En` fallback and must be 720p.\n\nRead [`docs/agents/servers-and-assets.md`](../../docs/agents/servers-and-assets.md).\n","docs/agents/automation-scripts.md":"# Writing automation logic\n\n## How a script run is wired (Hilt)\n\nTwo long-lived services: `ScriptRunnerService` (foreground service owning the overlay,\nnotification, and `ScriptManager`) and `TapperService` (AccessibilityService providing\ngestures).\n\nEach *run* gets its own Hilt component: `ScriptComponent` (`@ScriptScope`, child of\n`ServiceComponent`, built via `ScriptComponentBuilder` with the live `ScreenshotService`\nbound in). `ScriptManager` builds it, pulls the chosen `EntryPoint` out through the\n`ScriptEntryPoint` Hilt entry point, runs it on its own thread, and tears the component\ndown at exit (including explicitly closing the Tesseract `OcrService`). Consequences:\n\n- `ScriptScope` singletons (`Locations`, `ScriptAreaTransforms`, `ScreenshotManager`,\n  `FgoAutomataApi`, …) are recreated per run, so screen-geometry values are computed once\n  per run and cached.\n- Adding a new script mode means: entry point class in `scripts/.../entrypoints/`, a getter\n  on `ScriptEntryPoint`, a `ScriptModeEnum` value, and a branch in\n  `ScriptManager.getEntryPoint`.\n- Bindings for a run live in `app/di/script/` (`LibAutomataModule` for `libautomata`\n  interfaces, `ScriptsModule` for FGO-level ones).\n\n## Script structure\n\nEntry points extend `EntryPoint` and implement `script(): Nothing`. `ExitManager` implements\nstop/pause by throwing `ScriptAbortException` from inside wait/click primitives, which is why\n`script()` never returns normally — exit and errors surface through `scriptExitListener`.\n\nThe dominant pattern (see `AutoBattle.loop()`) is a **map of screen detectors to\nhandlers**, evaluated in order inside `useSameSnapIn { }`, one action per iteration, then a\nshort wait. Order matters: earlier entries win. Detectors must be cheap image/color checks,\nnot stateful logic.\n\nMix in `IFgoAutomataApi` (delegating to an injected `FgoAutomataApi`) to get `prefs`,\n`images`, `locations`, `messages`, plus the `Region`/`Location` extension DSL:\n`images[Images.Menu] in someRegion`, `region.exists(pattern, timeout)`,\n`region.waitVanish(...)`, `location.click()`, `duration.wait()`.\n\n- **`useSameSnapIn { }`** caches one screenshot for the whole block. Any group of checks\n  that must agree on a single frame has to be inside it; without it every `exists` takes a\n  fresh screenshot (slow, and racy across animations).\n- **User-facing text** never goes in `scripts/` (no Android `R`). Emit a\n  `ScriptNotify`/`ScriptLog` through `messages: IScriptMessages`;\n  `app/util/ScriptMessages.kt` maps it to a localized string, toast, or notification.\n- **Never hardcode wait/similarity magic numbers casually** — `PlatformPrefs` exposes\n  user-tunable `minSimilarity`, `waitMultiplier`, `swipeMultiplier` (the Fine-Tune screen).\n  Explicit `similarity` arguments in `Region.exists` should be rare; anything under ~0.65\n  means the template image is wrong.\n","docs/agents/README.md":"# Agent topic guides\n\nThe path-specific half of this repo's AI-agent guidance; the always-loaded half is\n[`AGENTS.md`](../../AGENTS.md) at the repo root. These files are the single source of\ntruth — keep them provider-neutral (plain prose, no frontmatter, no tool names, no\n`@`-imports) so any agent can read them, and never copy their prose elsewhere.\n\nThree layers point at them, so an agent only pulls in a topic when it is relevant:\n\n| Layer | Files | Mechanism |\n| --- | --- | --- |\n| Universal | `AGENTS.md` router section | Every agent reads the root file; each entry names a trigger and the guide to read. |\n| GitHub Copilot | `.github/instructions/*.instructions.md` | `applyTo:` glob, applied when a matching file is in context. |\n| Claude Code | `.claude/rules/*.md` | `paths:` glob, loaded when the model touches a matching file. |\n\nThe vendor files are stubs: a glob, a one-line reason, a pointer here. Adding a guide means\na stub in each vendor directory and a trigger line in the root router; adding an agent\n(Cursor's `.cursor/rules/*.mdc`, Windsurf's `.windsurf/rules/`) means copying a stub and\nrenaming the glob key.\n","docs/agents/coordinates.md":"# Subagent: coordinates\n\nPath: `docs/agents/coordinates.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/coordinates.md)","docs/agents/dependencies.md":"# Subagent: dependencies\n\nPath: `docs/agents/dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/dependencies.md)","docs/agents/github-workflows.md":"# Subagent: github-workflows\n\nPath: `docs/agents/github-workflows.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/github-workflows.md)","docs/agents/servers-and-assets.md":"# Subagent: servers-and-assets\n\nPath: `docs/agents/servers-and-assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/servers-and-assets.md)","docs/agents/translations.md":"# Subagent: translations\n\nPath: `docs/agents/translations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/translations.md)",".claude/skills/extract-template-asset/crop_template.py":"# Claude Directive: crop_template.py\n\nPath: `.claude/skills/extract-template-asset/crop_template.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/skills/extract-template-asset/crop_template.py)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nPath-specific guidance lives in [`docs/agents/`](docs/agents) so it is loaded only when relevant: read a guide when its trigger below matches. Copilot and Claude Code pick the same guides up automatically via stubs in `.github/instructions/` and `.claude/rules/` — see [`docs/agents/README.md`](docs/agents/README.md).\n\n## What this is\n\nFate/Grand Automata (FGA) — an Android app that automates farming in the game *Fate/Grand Order*. It does **not** touch the game process: it reads the screen via MediaProjection, matches templates with OpenCV (plus Tesseract OCR), and taps/swipes through an AccessibilityService. Android 7+ (minSdk 24), no root required (root screenshots are an optional path).\n\n## Topic guides — read before touching these paths\n\n- **Any on-screen coordinate, any file in `scripts/.../locations/`, any template image added to `app/src/main/assets/`** — [`docs/agents/coordinates.md`](docs/agents/coordinates.md). Matching runs at 720p, coordinates are 1440p, and the X origin depends on aspect ratio.\n- **`gradle/libs.versions.toml`, the Gradle wrapper, any `build.gradle.kts`** — [`docs/agents/dependencies.md`](docs/agents/dependencies.md). The AGP/Gradle/Kotlin/androidx bumps only succeed in one order; it also holds the JDK, bytecode-target and `compileSdk` pins.\n- **Automation logic in `scripts/`, or the Hilt wiring in `app/di/` and `app/runner/`** — [`docs/agents/automation-scripts.md`](docs/agents/automation-scripts.md). Every run gets its own Hilt component, and `script()` never returns normally.\n- **Any file in `app/src/main/assets/`** — [`docs/agents/servers-and-assets.md`](docs/agents/servers-and-assets.md). Template images are per-server with an `En` fallback; server choice and app language are independent.\n- **Strings under `app/src/main/res/`** — [`docs/agents/translations.md`](docs/agents/translations.md). English strings live in `values/localized.xml`; the translated `values-*` copies are synced from POEditor.\n- **Any file in `.github/workflows/`** — [`docs/agents/github-workflows.md`](docs/agents/github-workflows.md). Every workflow change must regenerate `actions.lock` with `gh actions-lock`.\n\n## Build & test\n\n```bash\n./gradlew :app:compileDebugKotlin # Compiles the kotlin code for the android app.\n./gradlew :scripts:test          # the only unit tests in the repo (JUnit 5 + assertk + mockk)\n./gradlew :scripts:test --tests '*SupportSelectionTest'          # single test class\n./gradlew :scripts:test --tests '*SupportSelectionTest.someName' # single test method\n./gradlew lint                   # abortOnError = false, so lint never fails the build\n./gradlew dependencyUpdates      # ben-manes versions plugin\n```\n\nEverything builds on **JDK 21** via a Gradle toolchain pin; emitted bytecode stays at **Java 11**.\n\n## Module layout and dependency direction\n\n```\napp  ──▶ scripts ──▶ libautomata\n └──▶ prefs ──▶ scripts\n```\n\n- **`libautomata/`** — pure JVM Kotlin (no Android deps). Game-agnostic automation primitives: `Region`, `Location`, `Size`, `Pattern`, `AutomataApi`/`StandardAutomataApi`, `ScreenshotManager`, `ImageMatcher`, `Clicker`, `Swiper`, `Waiter`, `Scale`, `GameAreaManager`, `EntryPoint`, `ExitManager`. Everything platform-specific is an interface (`ScreenshotService`, `GestureService`, `OcrService`, `PlatformImpl`, `PlatformPrefs`) implemented in `app`.\n- **`scripts/`** — pure JVM Kotlin. All FGO knowledge: entry points, per-screen modules, coordinates, image names, prefs *interfaces*. This is where automation logic belongs, and the only module with tests (it's testable precisely because it has no Android dependency — keep it that way).\n- **`prefs/`** — Android library implementing the `I*Preferences` interfaces from `scripts` on top of `flow-preferences`. `PrefsCore` holds the raw `Pref<T>` delegates; `PreferencesImpl` adapts them to the script-facing interfaces.\n- **`app/`** — Compose UI, Hilt wiring, the two services, and the Android implementations of the `libautomata` interfaces (`imaging/`, `accessibility/`, `root/`, `util/`).\n\n## Repo conventions\n\n- Kotlin official code style; `kotlin.time.Duration` is used everywhere for waits.\n- Hilt + KSP for DI; Compose (BOM-managed) with several `ExperimentalX` opt-ins already enabled in `app/build.gradle.kts`.\n- Dependencies live in `gradle/libs.versions.toml` — always add/bump there, never inline coordinates.\n- `wiki/` is the source of the GitHub wiki (published by `.github/workflows/publish-wiki.yml`) — user-facing docs go there, not in the README.\n- Agent guidance: keep this file to what's true repo-wide and put path-specific rules in [`docs/agents/`](docs/agents), following [`docs/agents/README.md`](docs/agents/README.md).\n","isInternal":false,"tokens":1183,"sizeBytes":4756},{"name":"automation-scripts.md","path":".claude/rules/automation-scripts.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/rules/automation-scripts.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths: \"scripts/**,app/src/main/java/io/github/fate_grand_automata/di/**,app/src/main/java/io/github/fate_grand_automata/runner/**\"\ndescription: How a script run is wired through Hilt, and the patterns automation logic follows.\n---\n\nEvery script run gets its own Hilt component, and `script()` never returns normally — it exits\nby exception.\n\nRead [`docs/agents/automation-scripts.md`](../../docs/agents/automation-scripts.md).\n","frontmatter":{"paths":"scripts/**,app/src/main/java/io/github/fate_grand_automata/di/**,app/src/main/java/io/github/fate_grand_automata/runner/**","description":"How a script run is wired through Hilt, and the patterns automation logic follows."},"isInternal":false,"tokens":104,"sizeBytes":434},{"name":"coordinates.md","path":".claude/rules/coordinates.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/rules/coordinates.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths: \"scripts/src/main/java/**/locations/*.kt,app/src/main/assets/**,libautomata/**\"\ndescription: The 720p/1440p coordinate systems used by image matching and Location/Region values.\n---\n\nImage matching runs at 720p, coordinates are expressed at 1440p, and the X origin depends on the\ndevice aspect ratio — guessing produces values that are wrong on most devices.\n\nRead [`docs/agents/coordinates.md`](../../docs/agents/coordinates.md).\n","frontmatter":{"paths":"scripts/src/main/java/**/locations/*.kt,app/src/main/assets/**,libautomata/**","description":"The 720p/1440p coordinate systems used by image matching and Location/Region values."},"isInternal":false,"tokens":104,"sizeBytes":444},{"name":"dependencies.md","path":".claude/rules/dependencies.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/rules/dependencies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths: \"gradle/libs.versions.toml,gradle/wrapper/**,**/build.gradle.kts,settings.gradle.kts,Gemfile,Gemfile.lock,.github/workflows/**\"\ndescription: Toolchain pins (JDK/bytecode/AGP/compileSdk) and the order dependency bumps must follow.\n---\n\nThe AGP, Gradle, Kotlin and androidx bumps are interlocked and only succeed in one order; the\nJDK, bytecode-target and `compileSdk` pins are deliberate.\n\nRead [`docs/agents/dependencies.md`](../../docs/agents/dependencies.md).\n","frontmatter":{"paths":"gradle/libs.versions.toml,gradle/wrapper/**,**/build.gradle.kts,settings.gradle.kts,Gemfile,Gemfile.lock,.github/workflows/**","description":"Toolchain pins (JDK/bytecode/AGP/compileSdk) and the order dependency bumps must follow."},"isInternal":false,"tokens":122,"sizeBytes":473},{"name":"github-workflows.md","path":".claude/rules/github-workflows.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/rules/github-workflows.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths: \".github/workflows/**\"\ndescription: Workflow changes must regenerate the actions lockfile.\n---\n\nEvery workflow change must be committed with a regenerated `.github/workflows/actions.lock`\n(`gh actions-lock`). Otherwise its runs are blocked by the repository's *Require lockfile* policy.\n\nRead [`docs/agents/github-workflows.md`](../../docs/agents/github-workflows.md).\n","frontmatter":{"paths":".github/workflows/**","description":"Workflow changes must regenerate the actions lockfile."},"isInternal":false,"tokens":77,"sizeBytes":380},{"name":"servers-and-assets.md","path":".claude/rules/servers-and-assets.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/rules/servers-and-assets.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths: \"app/src/main/assets/**\"\ndescription: Per-server template image folders and the En fallback.\n---\n\nTemplate images are per-server with an `En` fallback and must be 720p.\n\nRead [`docs/agents/servers-and-assets.md`](../../docs/agents/servers-and-assets.md).\n","frontmatter":{"paths":"app/src/main/assets/**","description":"Per-server template image folders and the En fallback."},"isInternal":false,"tokens":62,"sizeBytes":266},{"name":"translations.md","path":".claude/rules/translations.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/rules/translations.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\npaths: \"app/src/main/res/**,app/src/main/assets/Support/**\"\ndescription: The English string source and the external POEditor translation flow.\n---\n\nEnglish strings live in `values/localized.xml` and are edited here; the translated\n`values-*` copies come from POEditor.\n\nRead [`docs/agents/translations.md`](../../docs/agents/translations.md).\n","frontmatter":{"paths":"app/src/main/res/**,app/src/main/assets/Support/**","description":"The English string source and the external POEditor translation flow."},"isInternal":false,"tokens":78,"sizeBytes":347},{"name":"SKILL.md","path":".claude/skills/extract-template-asset/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/skills/extract-template-asset/SKILL.md","title":"extract-template-asset","category":"anthropic-skill","format":"markdown","content":"---\nname: extract-template-asset\ndescription: Cut a 720p template image for app/src/main/assets out of a game screenshot, using a Region from scripts/.../locations. Use when adding or fixing a template for a server (En, Jp, Cn, Tw, Kr) - \"add a Kr asset\", \"the image isn't detected on TW\", \"make a template for X\" - or when checking that an existing Region actually covers what it should.\n---\n\n# Extracting a template asset from a screenshot\n\nRead [`docs/agents/coordinates.md`](../../../docs/agents/coordinates.md) first for the two\ncoordinate systems. This skill is the mechanical procedure on top of it.\n\n`crop_template.py` (next to this file, needs Pillow) reproduces `FgoGameAreaManager` and\n`RealScale`, so a `Region` copied out of `locations/*.kt` resolves to the pixels the script\nactually searches. Do the arithmetic with it rather than by hand - the game-area rules are\nwhere this goes wrong.\n\n## Before cropping\n\n1. **Find the Region** the script searches, in `scripts/.../locations/*.kt`. Note the\n   `xFromRight()` / `xFromCenter()` / `yFromBottom()` suffix and whether it sits behind an\n   `if (isWide)` branch - screenshots wider than 18:9 usually take a different Region.\n2. **Find the caller** in `scripts/.../modules/`. Two things change what you cut:\n   - Matching is grayscale unless the `find` is inside `useColor { }`.\n   - Some callers click relative to the *match*, not a fixed location -\n     `Caster.openCommandSpellMenu` uses `Location(match.region.x, 150)`, so the template's\n     left edge decides where the tap lands.\n3. **Get the screenshot** from `docs/reference-images/<Server>/`, and put any new one there\n   so the template can be re-cut later - see\n   [`docs/agents/servers-and-assets.md`](../../../docs/agents/servers-and-assets.md).\n   Those are stored at 720p; commit a contributed screenshot downscaled, not raw. The script\n   downscales whatever it is given, so it works either way.\n4. **Look at the existing assets** for the same `Images` entry. Match their convention;\n   they are usually a tight crop of the localized label, not the icon. `En` is the\n   fallback, so only add a server file when the art genuinely differs.\n\n## Cropping\n\nSource screenshots live in `docs/reference-images/<Server>/`. Stage 1 - see what the region\ncovers:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right --out-dir /tmp/cs\n```\n\nThis writes `*-region.png` (the search region at 720p) and `*-region-grid.png` (the same\nthing upscaled with a labelled 5px grid). If the region does not sit on the target, the\nRegion is wrong or you picked the wrong `isWide` branch - fix that before going further.\n\nStage 2 - read the template box off the grid image and cut it:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right \\\n    --template 48,31,21,11 --click-y 150 \\\n    --out app/src/main/assets/Kr/command_spell.png\n```\n\n`--template` is in pixels relative to the region crop's top-left. `--click-y` marks where a\nmatch-anchored click would land on the verification image.\n\n## Choosing the box\n\n**Only include pixels that are part of the UI.** The battle scene behind a HUD element\nchanges every quest - the same element sits on a night sky on one server's screenshot and\non green foliage on another. Any background you include is noise that will not match. This\nis why the existing templates are small and sit inside glyph strokes.\n\nJudge coverage by eye on the grid image. Automated \"how much of this box is glyph\" scoring\nis unreliable, because dark scenery reads the same as a dark outline.\n\nPrefer a box that is entirely inside dense artwork - the filled part of a character, a\nsolid bar - over a larger box that reaches into the scene at its corners.\n\n## Checking the result\n\nOpen `*-verify.png`. The yellow box is the search region, green is the template, red is the\nclick. Confirm the region sits on the target, the template is all UI, and the click lands on\nsomething tappable. Also confirm the written file is 720p-scale: a template cut this way is\nhalf the Region's script dimensions, so `Region(..., 190, 120)` can hold at most 95x60.","frontmatter":{"name":"extract-template-asset","description":"Cut a 720p template image for app/src/main/assets out of a game screenshot, using a Region from scripts/.../locations. Use when adding or fixing a template for a server (En, Jp, Cn, Tw, Kr) - \"add a Kr asset\", \"the image isn't detected on TW\", \"make a template for X\" - or when checking that an existing Region actually covers what it should."},"isInternal":false,"tokens":1063,"sizeBytes":4294},{"name":"crop_template.py","path":".claude/skills/extract-template-asset/crop_template.py","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/skills/extract-template-asset/crop_template.py","title":"Claude Directive: crop_template.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: crop_template.py\n\nPath: `.claude/skills/extract-template-asset/crop_template.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/skills/extract-template-asset/crop_template.py)","tokens":300,"sizeBytes":249},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# Copilot instructions\n\nThe repo-wide guidance for this project lives in [`AGENTS.md`](../AGENTS.md) — read it first.\nPath-specific guidance lives in [`docs/agents/`](../docs/agents/); the files in\n[`.github/instructions/`](instructions/) point at it with an `applyTo` glob.\n","isInternal":false,"tokens":70,"sizeBytes":277},{"name":"automation-scripts.md","path":"docs/agents/automation-scripts.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/automation-scripts.md","title":"Subagent: automation-scripts","category":"subagent-persona","format":"markdown","content":"# Writing automation logic\n\n## How a script run is wired (Hilt)\n\nTwo long-lived services: `ScriptRunnerService` (foreground service owning the overlay,\nnotification, and `ScriptManager`) and `TapperService` (AccessibilityService providing\ngestures).\n\nEach *run* gets its own Hilt component: `ScriptComponent` (`@ScriptScope`, child of\n`ServiceComponent`, built via `ScriptComponentBuilder` with the live `ScreenshotService`\nbound in). `ScriptManager` builds it, pulls the chosen `EntryPoint` out through the\n`ScriptEntryPoint` Hilt entry point, runs it on its own thread, and tears the component\ndown at exit (including explicitly closing the Tesseract `OcrService`). Consequences:\n\n- `ScriptScope` singletons (`Locations`, `ScriptAreaTransforms`, `ScreenshotManager`,\n  `FgoAutomataApi`, …) are recreated per run, so screen-geometry values are computed once\n  per run and cached.\n- Adding a new script mode means: entry point class in `scripts/.../entrypoints/`, a getter\n  on `ScriptEntryPoint`, a `ScriptModeEnum` value, and a branch in\n  `ScriptManager.getEntryPoint`.\n- Bindings for a run live in `app/di/script/` (`LibAutomataModule` for `libautomata`\n  interfaces, `ScriptsModule` for FGO-level ones).\n\n## Script structure\n\nEntry points extend `EntryPoint` and implement `script(): Nothing`. `ExitManager` implements\nstop/pause by throwing `ScriptAbortException` from inside wait/click primitives, which is why\n`script()` never returns normally — exit and errors surface through `scriptExitListener`.\n\nThe dominant pattern (see `AutoBattle.loop()`) is a **map of screen detectors to\nhandlers**, evaluated in order inside `useSameSnapIn { }`, one action per iteration, then a\nshort wait. Order matters: earlier entries win. Detectors must be cheap image/color checks,\nnot stateful logic.\n\nMix in `IFgoAutomataApi` (delegating to an injected `FgoAutomataApi`) to get `prefs`,\n`images`, `locations`, `messages`, plus the `Region`/`Location` extension DSL:\n`images[Images.Menu] in someRegion`, `region.exists(pattern, timeout)`,\n`region.waitVanish(...)`, `location.click()`, `duration.wait()`.\n\n- **`useSameSnapIn { }`** caches one screenshot for the whole block. Any group of checks\n  that must agree on a single frame has to be inside it; without it every `exists` takes a\n  fresh screenshot (slow, and racy across animations).\n- **User-facing text** never goes in `scripts/` (no Android `R`). Emit a\n  `ScriptNotify`/`ScriptLog` through `messages: IScriptMessages`;\n  `app/util/ScriptMessages.kt` maps it to a localized string, toast, or notification.\n- **Never hardcode wait/similarity magic numbers casually** — `PlatformPrefs` exposes\n  user-tunable `minSimilarity`, `waitMultiplier`, `swipeMultiplier` (the Fine-Tune screen).\n  Explicit `similarity` arguments in `Region.exists` should be rare; anything under ~0.65\n  means the template image is wrong.\n","isInternal":false,"tokens":702,"sizeBytes":2869},{"name":"coordinates.md","path":"docs/agents/coordinates.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/coordinates.md","title":"Subagent: coordinates","category":"subagent-persona","format":"markdown","content":"# Subagent: coordinates\n\nPath: `docs/agents/coordinates.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/coordinates.md)","tokens":300,"sizeBytes":180},{"name":"dependencies.md","path":"docs/agents/dependencies.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/dependencies.md","title":"Subagent: dependencies","category":"subagent-persona","format":"markdown","content":"# Subagent: dependencies\n\nPath: `docs/agents/dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/dependencies.md)","tokens":300,"sizeBytes":183},{"name":"github-workflows.md","path":"docs/agents/github-workflows.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/github-workflows.md","title":"Subagent: github-workflows","category":"subagent-persona","format":"markdown","content":"# Subagent: github-workflows\n\nPath: `docs/agents/github-workflows.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/github-workflows.md)","tokens":300,"sizeBytes":195},{"name":"README.md","path":"docs/agents/README.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/README.md","title":"Subagent: readme","category":"subagent-persona","format":"markdown","content":"# Agent topic guides\n\nThe path-specific half of this repo's AI-agent guidance; the always-loaded half is\n[`AGENTS.md`](../../AGENTS.md) at the repo root. These files are the single source of\ntruth — keep them provider-neutral (plain prose, no frontmatter, no tool names, no\n`@`-imports) so any agent can read them, and never copy their prose elsewhere.\n\nThree layers point at them, so an agent only pulls in a topic when it is relevant:\n\n| Layer | Files | Mechanism |\n| --- | --- | --- |\n| Universal | `AGENTS.md` router section | Every agent reads the root file; each entry names a trigger and the guide to read. |\n| GitHub Copilot | `.github/instructions/*.instructions.md` | `applyTo:` glob, applied when a matching file is in context. |\n| Claude Code | `.claude/rules/*.md` | `paths:` glob, loaded when the model touches a matching file. |\n\nThe vendor files are stubs: a glob, a one-line reason, a pointer here. Adding a guide means\na stub in each vendor directory and a trigger line in the root router; adding an agent\n(Cursor's `.cursor/rules/*.mdc`, Windsurf's `.windsurf/rules/`) means copying a stub and\nrenaming the glob key.\n","isInternal":false,"tokens":282,"sizeBytes":1138},{"name":"servers-and-assets.md","path":"docs/agents/servers-and-assets.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/servers-and-assets.md","title":"Subagent: servers-and-assets","category":"subagent-persona","format":"markdown","content":"# Subagent: servers-and-assets\n\nPath: `docs/agents/servers-and-assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/servers-and-assets.md)","tokens":300,"sizeBytes":201},{"name":"translations.md","path":"docs/agents/translations.md","rawUrl":"https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/translations.md","title":"Subagent: translations","category":"subagent-persona","format":"markdown","content":"# Subagent: translations\n\nPath: `docs/agents/translations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/translations.md)","tokens":300,"sizeBytes":183}],"systemPromptSnippet":"<yakaai_skills repo=\"Fate-Grand-Automata/FGA\">\n<!-- File: AGENTS.md (Tokens: ~1183 | Category: root-instruction) -->\n# AGENTS.md\n\nPath-specific guidance lives in [`docs/agents/`](docs/agents) so it is loaded only when relevant: read a guide when its trigger below matches. Copilot and Claude Code pick the same guides up automatically via stubs in `.github/instructions/` and `.claude/rules/` — see [`docs/agents/README.md`](docs/agents/README.md).\n\n## What this is\n\nFate/Grand Automata (FGA) — an Android app that automates farming in the game *Fate/Grand Order*. It does **not** touch the game process: it reads the screen via MediaProjection, matches templates with OpenCV (plus Tesseract OCR), and taps/swipes through an AccessibilityService. Android 7+ (minSdk 24), no root required (root screenshots are an optional path).\n\n## Topic guides — read before touching these paths\n\n- **Any on-screen coordinate, any file in `scripts/.../locations/`, any template image added to `app/src/main/assets/`** — [`docs/agents/coordinates.md`](docs/agents/coordinates.md). Matching runs at 720p, coordinates are 1440p, and the X origin depends on aspect ratio.\n- **`gradle/libs.versions.toml`, the Gradle wrapper, any `build.gradle.kts`** — [`docs/agents/dependencies.md`](docs/agents/dependencies.md). The AGP/Gradle/Kotlin/androidx bumps only succeed in one order; it also holds the JDK, bytecode-target and `compileSdk` pins.\n- **Automation logic in `scripts/`, or the Hilt wiring in `app/di/` and `app/runner/`** — [`docs/agents/automation-scripts.md`](docs/agents/automation-scripts.md). Every run gets its own Hilt component, and `script()` never returns normally.\n- **Any file in `app/src/main/assets/`** — [`docs/agents/servers-and-assets.md`](docs/agents/servers-and-assets.md). Template images are per-server with an `En` fallback; server choice and app language are independent.\n- **Strings under `app/src/main/res/`** — [`docs/agents/translations.md`](docs/agents/translations.md). English strings live in `values/localized.xml`; the translated `values-*` copies are synced from POEditor.\n- **Any file in `.github/workflows/`** — [`docs/agents/github-workflows.md`](docs/agents/github-workflows.md). Every workflow change must regenerate `actions.lock` with `gh actions-lock`.\n\n## Build & test\n\n```bash\n./gradlew :app:compileDebugKotlin # Compiles the kotlin code for the android app.\n./gradlew :scripts:test          # the only unit tests in the repo (JUnit 5 + assertk + mockk)\n./gradlew :scripts:test --tests '*SupportSelectionTest'          # single test class\n./gradlew :scripts:test --tests '*SupportSelectionTest.someName' # single test method\n./gradlew lint                   # abortOnError = false, so lint never fails the build\n./gradlew dependencyUpdates      # ben-manes versions plugin\n```\n\nEverything builds on **JDK 21** via a Gradle toolchain pin; emitted bytecode stays at **Java 11**.\n\n## Module layout and dependency direction\n\n```\napp  ──▶ scripts ──▶ libautomata\n └──▶ prefs ──▶ scripts\n```\n\n- **`libautomata/`** — pure JVM Kotlin (no Android deps). Game-agnostic automation primitives: `Region`, `Location`, `Size`, `Pattern`, `AutomataApi`/`StandardAutomataApi`, `ScreenshotManager`, `ImageMatcher`, `Clicker`, `Swiper`, `Waiter`, `Scale`, `GameAreaManager`, `EntryPoint`, `ExitManager`. Everything platform-specific is an interface (`ScreenshotService`, `GestureService`, `OcrService`, `PlatformImpl`, `PlatformPrefs`) implemented in `app`.\n- **`scripts/`** — pure JVM Kotlin. All FGO knowledge: entry points, per-screen modules, coordinates, image names, prefs *interfaces*. This is where automation logic belongs, and the only module with tests (it's testable precisely because it has no Android dependency — keep it that way).\n- **`prefs/`** — Android library implementing the `I*Preferences` interfaces from `scripts` on top of `flow-preferences`. `PrefsCore` holds the raw `Pref<T>` delegates; `PreferencesImpl` adapts them to the script-facing interfaces.\n- **`app/`** — Compose UI, Hilt wiring, the two services, and the Android implementations of the `libautomata` interfaces (`imaging/`, `accessibility/`, `root/`, `util/`).\n\n## Repo conventions\n\n- Kotlin official code style; `kotlin.time.Duration` is used everywhere for waits.\n- Hilt + KSP for DI; Compose (BOM-managed) with several `ExperimentalX` opt-ins already enabled in `app/build.gradle.kts`.\n- Dependencies live in `gradle/libs.versions.toml` — always add/bump there, never inline coordinates.\n- `wiki/` is the source of the GitHub wiki (published by `.github/workflows/publish-wiki.yml`) — user-facing docs go there, not in the README.\n- Agent guidance: keep this file to what's true repo-wide and put path-specific rules in [`docs/agents/`](docs/agents), following [`docs/agents/README.md`](docs/agents/README.md).\n\n<!-- File: .claude/rules/automation-scripts.md (Tokens: ~104 | Category: anthropic-skill) -->\n---\npaths: \"scripts/**,app/src/main/java/io/github/fate_grand_automata/di/**,app/src/main/java/io/github/fate_grand_automata/runner/**\"\ndescription: How a script run is wired through Hilt, and the patterns automation logic follows.\n---\n\nEvery script run gets its own Hilt component, and `script()` never returns normally — it exits\nby exception.\n\nRead [`docs/agents/automation-scripts.md`](../../docs/agents/automation-scripts.md).\n\n<!-- File: .claude/rules/coordinates.md (Tokens: ~104 | Category: anthropic-skill) -->\n---\npaths: \"scripts/src/main/java/**/locations/*.kt,app/src/main/assets/**,libautomata/**\"\ndescription: The 720p/1440p coordinate systems used by image matching and Location/Region values.\n---\n\nImage matching runs at 720p, coordinates are expressed at 1440p, and the X origin depends on the\ndevice aspect ratio — guessing produces values that are wrong on most devices.\n\nRead [`docs/agents/coordinates.md`](../../docs/agents/coordinates.md).\n\n<!-- File: .claude/rules/dependencies.md (Tokens: ~122 | Category: anthropic-skill) -->\n---\npaths: \"gradle/libs.versions.toml,gradle/wrapper/**,**/build.gradle.kts,settings.gradle.kts,Gemfile,Gemfile.lock,.github/workflows/**\"\ndescription: Toolchain pins (JDK/bytecode/AGP/compileSdk) and the order dependency bumps must follow.\n---\n\nThe AGP, Gradle, Kotlin and androidx bumps are interlocked and only succeed in one order; the\nJDK, bytecode-target and `compileSdk` pins are deliberate.\n\nRead [`docs/agents/dependencies.md`](../../docs/agents/dependencies.md).\n\n<!-- File: .claude/rules/github-workflows.md (Tokens: ~77 | Category: anthropic-skill) -->\n---\npaths: \".github/workflows/**\"\ndescription: Workflow changes must regenerate the actions lockfile.\n---\n\nEvery workflow change must be committed with a regenerated `.github/workflows/actions.lock`\n(`gh actions-lock`). Otherwise its runs are blocked by the repository's *Require lockfile* policy.\n\nRead [`docs/agents/github-workflows.md`](../../docs/agents/github-workflows.md).\n\n<!-- File: .claude/rules/servers-and-assets.md (Tokens: ~62 | Category: anthropic-skill) -->\n---\npaths: \"app/src/main/assets/**\"\ndescription: Per-server template image folders and the En fallback.\n---\n\nTemplate images are per-server with an `En` fallback and must be 720p.\n\nRead [`docs/agents/servers-and-assets.md`](../../docs/agents/servers-and-assets.md).\n\n<!-- File: .claude/rules/translations.md (Tokens: ~78 | Category: anthropic-skill) -->\n---\npaths: \"app/src/main/res/**,app/src/main/assets/Support/**\"\ndescription: The English string source and the external POEditor translation flow.\n---\n\nEnglish strings live in `values/localized.xml` and are edited here; the translated\n`values-*` copies come from POEditor.\n\nRead [`docs/agents/translations.md`](../../docs/agents/translations.md).\n\n<!-- File: .claude/skills/extract-template-asset/SKILL.md (Tokens: ~1063 | Category: anthropic-skill) -->\n---\nname: extract-template-asset\ndescription: Cut a 720p template image for app/src/main/assets out of a game screenshot, using a Region from scripts/.../locations. Use when adding or fixing a template for a server (En, Jp, Cn, Tw, Kr) - \"add a Kr asset\", \"the image isn't detected on TW\", \"make a template for X\" - or when checking that an existing Region actually covers what it should.\n---\n\n# Extracting a template asset from a screenshot\n\nRead [`docs/agents/coordinates.md`](../../../docs/agents/coordinates.md) first for the two\ncoordinate systems. This skill is the mechanical procedure on top of it.\n\n`crop_template.py` (next to this file, needs Pillow) reproduces `FgoGameAreaManager` and\n`RealScale`, so a `Region` copied out of `locations/*.kt` resolves to the pixels the script\nactually searches. Do the arithmetic with it rather than by hand - the game-area rules are\nwhere this goes wrong.\n\n## Before cropping\n\n1. **Find the Region** the script searches, in `scripts/.../locations/*.kt`. Note the\n   `xFromRight()` / `xFromCenter()` / `yFromBottom()` suffix and whether it sits behind an\n   `if (isWide)` branch - screenshots wider than 18:9 usually take a different Region.\n2. **Find the caller** in `scripts/.../modules/`. Two things change what you cut:\n   - Matching is grayscale unless the `find` is inside `useColor { }`.\n   - Some callers click relative to the *match*, not a fixed location -\n     `Caster.openCommandSpellMenu` uses `Location(match.region.x, 150)`, so the template's\n     left edge decides where the tap lands.\n3. **Get the screenshot** from `docs/reference-images/<Server>/`, and put any new one there\n   so the template can be re-cut later - see\n   [`docs/agents/servers-and-assets.md`](../../../docs/agents/servers-and-assets.md).\n   Those are stored at 720p; commit a contributed screenshot downscaled, not raw. The script\n   downscales whatever it is given, so it works either way.\n4. **Look at the existing assets** for the same `Images` entry. Match their convention;\n   they are usually a tight crop of the localized label, not the icon. `En` is the\n   fallback, so only add a server file when the art genuinely differs.\n\n## Cropping\n\nSource screenshots live in `docs/reference-images/<Server>/`. Stage 1 - see what the region\ncovers:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right --out-dir /tmp/cs\n```\n\nThis writes `*-region.png` (the search region at 720p) and `*-region-grid.png` (the same\nthing upscaled with a labelled 5px grid). If the region does not sit on the target, the\nRegion is wrong or you picked the wrong `isWide` branch - fix that before going further.\n\nStage 2 - read the template box off the grid image and cut it:\n\n```bash\npython3 .claude/skills/extract-template-asset/crop_template.py \\\n    docs/reference-images/Kr/battle-screen.png \\\n    --region -590,180,190,120 --anchor right \\\n    --template 48,31,21,11 --click-y 150 \\\n    --out app/src/main/assets/Kr/command_spell.png\n```\n\n`--template` is in pixels relative to the region crop's top-left. `--click-y` marks where a\nmatch-anchored click would land on the verification image.\n\n## Choosing the box\n\n**Only include pixels that are part of the UI.** The battle scene behind a HUD element\nchanges every quest - the same element sits on a night sky on one server's screenshot and\non green foliage on another. Any background you include is noise that will not match. This\nis why the existing templates are small and sit inside glyph strokes.\n\nJudge coverage by eye on the grid image. Automated \"how much of this box is glyph\" scoring\nis unreliable, because dark scenery reads the same as a dark outline.\n\nPrefer a box that is entirely inside dense artwork - the filled part of a character, a\nsolid bar - over a larger box that reaches into the scene at its corners.\n\n## Checking the result\n\nOpen `*-verify.png`. The yellow box is the search region, green is the template, red is the\nclick. Confirm the region sits on the target, the template is all UI, and the click lands on\nsomething tappable. Also confirm the written file is 720p-scale: a template cut this way is\nhalf the Region's script dimensions, so `Region(..., 190, 120)` can hold at most 95x60.\n\n<!-- File: .claude/skills/extract-template-asset/crop_template.py (Tokens: ~300 | Category: claude-rule) -->\n# Claude Directive: crop_template.py\n\nPath: `.claude/skills/extract-template-asset/crop_template.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/.claude/skills/extract-template-asset/crop_template.py)\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~70 | Category: copilot-instructions) -->\n# Copilot instructions\n\nThe repo-wide guidance for this project lives in [`AGENTS.md`](../AGENTS.md) — read it first.\nPath-specific guidance lives in [`docs/agents/`](../docs/agents/); the files in\n[`.github/instructions/`](instructions/) point at it with an `applyTo` glob.\n\n<!-- File: docs/agents/automation-scripts.md (Tokens: ~702 | Category: subagent-persona) -->\n# Writing automation logic\n\n## How a script run is wired (Hilt)\n\nTwo long-lived services: `ScriptRunnerService` (foreground service owning the overlay,\nnotification, and `ScriptManager`) and `TapperService` (AccessibilityService providing\ngestures).\n\nEach *run* gets its own Hilt component: `ScriptComponent` (`@ScriptScope`, child of\n`ServiceComponent`, built via `ScriptComponentBuilder` with the live `ScreenshotService`\nbound in). `ScriptManager` builds it, pulls the chosen `EntryPoint` out through the\n`ScriptEntryPoint` Hilt entry point, runs it on its own thread, and tears the component\ndown at exit (including explicitly closing the Tesseract `OcrService`). Consequences:\n\n- `ScriptScope` singletons (`Locations`, `ScriptAreaTransforms`, `ScreenshotManager`,\n  `FgoAutomataApi`, …) are recreated per run, so screen-geometry values are computed once\n  per run and cached.\n- Adding a new script mode means: entry point class in `scripts/.../entrypoints/`, a getter\n  on `ScriptEntryPoint`, a `ScriptModeEnum` value, and a branch in\n  `ScriptManager.getEntryPoint`.\n- Bindings for a run live in `app/di/script/` (`LibAutomataModule` for `libautomata`\n  interfaces, `ScriptsModule` for FGO-level ones).\n\n## Script structure\n\nEntry points extend `EntryPoint` and implement `script(): Nothing`. `ExitManager` implements\nstop/pause by throwing `ScriptAbortException` from inside wait/click primitives, which is why\n`script()` never returns normally — exit and errors surface through `scriptExitListener`.\n\nThe dominant pattern (see `AutoBattle.loop()`) is a **map of screen detectors to\nhandlers**, evaluated in order inside `useSameSnapIn { }`, one action per iteration, then a\nshort wait. Order matters: earlier entries win. Detectors must be cheap image/color checks,\nnot stateful logic.\n\nMix in `IFgoAutomataApi` (delegating to an injected `FgoAutomataApi`) to get `prefs`,\n`images`, `locations`, `messages`, plus the `Region`/`Location` extension DSL:\n`images[Images.Menu] in someRegion`, `region.exists(pattern, timeout)`,\n`region.waitVanish(...)`, `location.click()`, `duration.wait()`.\n\n- **`useSameSnapIn { }`** caches one screenshot for the whole block. Any group of checks\n  that must agree on a single frame has to be inside it; without it every `exists` takes a\n  fresh screenshot (slow, and racy across animations).\n- **User-facing text** never goes in `scripts/` (no Android `R`). Emit a\n  `ScriptNotify`/`ScriptLog` through `messages: IScriptMessages`;\n  `app/util/ScriptMessages.kt` maps it to a localized string, toast, or notification.\n- **Never hardcode wait/similarity magic numbers casually** — `PlatformPrefs` exposes\n  user-tunable `minSimilarity`, `waitMultiplier`, `swipeMultiplier` (the Fine-Tune screen).\n  Explicit `similarity` arguments in `Region.exists` should be rare; anything under ~0.65\n  means the template image is wrong.\n\n<!-- File: docs/agents/coordinates.md (Tokens: ~300 | Category: subagent-persona) -->\n# Subagent: coordinates\n\nPath: `docs/agents/coordinates.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/coordinates.md)\n\n<!-- File: docs/agents/dependencies.md (Tokens: ~300 | Category: subagent-persona) -->\n# Subagent: dependencies\n\nPath: `docs/agents/dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/dependencies.md)\n\n<!-- File: docs/agents/github-workflows.md (Tokens: ~300 | Category: subagent-persona) -->\n# Subagent: github-workflows\n\nPath: `docs/agents/github-workflows.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/github-workflows.md)\n\n<!-- File: docs/agents/README.md (Tokens: ~282 | Category: subagent-persona) -->\n# Agent topic guides\n\nThe path-specific half of this repo's AI-agent guidance; the always-loaded half is\n[`AGENTS.md`](../../AGENTS.md) at the repo root. These files are the single source of\ntruth — keep them provider-neutral (plain prose, no frontmatter, no tool names, no\n`@`-imports) so any agent can read them, and never copy their prose elsewhere.\n\nThree layers point at them, so an agent only pulls in a topic when it is relevant:\n\n| Layer | Files | Mechanism |\n| --- | --- | --- |\n| Universal | `AGENTS.md` router section | Every agent reads the root file; each entry names a trigger and the guide to read. |\n| GitHub Copilot | `.github/instructions/*.instructions.md` | `applyTo:` glob, applied when a matching file is in context. |\n| Claude Code | `.claude/rules/*.md` | `paths:` glob, loaded when the model touches a matching file. |\n\nThe vendor files are stubs: a glob, a one-line reason, a pointer here. Adding a guide means\na stub in each vendor directory and a trigger line in the root router; adding an agent\n(Cursor's `.cursor/rules/*.mdc`, Windsurf's `.windsurf/rules/`) means copying a stub and\nrenaming the glob key.\n\n<!-- File: docs/agents/servers-and-assets.md (Tokens: ~300 | Category: subagent-persona) -->\n# Subagent: servers-and-assets\n\nPath: `docs/agents/servers-and-assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/servers-and-assets.md)\n\n<!-- File: docs/agents/translations.md (Tokens: ~300 | Category: subagent-persona) -->\n# Subagent: translations\n\nPath: `docs/agents/translations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Fate-Grand-Automata/FGA/HEAD/docs/agents/translations.md)\n</yakaai_skills>"}