# ONBOARDING.md — Start here

You are an agent joining the **Flyff Universe bot suite**. Follow this in order.
It's a **multi-mode** bot run from a tabbed control panel: **grind** (vision auto-attack +
food heal), **heal / heal+buff** (a follower Ringmaster: heal-spam + two-class buff rotation),
and **ShopScanner** (OCR private shops → a price database). Python + Playwright (trusted CDP
input), OpenCV + Tesseract OCR sensing. It's also packaged to **share** (`install.bat`).

## 1. Read these, in this order
1. `PROJECT_OVERVIEW.md` — what/why, the canvas/WASM/WebSocket constraint, the
   **connection-layer decision (Playwright over CDP)**, the modular architecture,
   targeting strategies, and the roadmap (M0–M12; M8–M11 — modes/panel/scanners/
   distribution — are **built**; harder-mob engage accuracy is the open vision work). **Read fully.**
2. `CLAUDE.md` — operational rules, environment quirks, conventions, gotchas.
3. Skim the repo for code added since these docs (`*.py`, config) and the roadmap
   status near the bottom of `PROJECT_OVERVIEW.md`.

## 2. Internalize the non-negotiables
- Flyff is **canvas/WASM over a binary WebSocket** → no DOM state. **Sense via
  screenshots → OpenCV; act via trusted CDP input.**
- **Connection = Playwright (Python) driving real Chromium.** Canvas/WASM games
  ignore *synthetic* events (`isTrusted === false`); CDP input is **trusted**, so
  the game accepts it. This is why extensions/userscripts are NOT the control
  layer. Don't reopen this decision without a strong reason.
- Chosen = **Approach A** (pixels + trusted input). WebSocket decoding (B) is
  optional/advanced; never craft outgoing packets.
- **No custom browser / no AWS yet**, but keep the core UI-agnostic + config-driven.
- **ToS/ban risk is real** — human-like input, timing jitter, throwaway account.
- Use `python` (not `py`). Run Chromium **headed**, `deviceScaleFactor=1`. All
  screen coordinates need a **calibration step**.

## 3. Verify the environment
```
python --version            # expect 3.12.x
python -m pip install -r requirements.txt    # playwright opencv-python numpy pytesseract
python -m playwright install chromium
winget install -e --id UB-Mannheim.tesseract-ocr   # OCR (status text + ShopScanner)
```
(For a fresh machine, **`install.bat`** does all of the above into a `.venv`.) Confirm with
the user: their **keybinds** (grind: `0`=attack, `1`=food, `2`=self-buff; heal: `1`=heal,
`0`=MP potion; heal+buff buffs across F2/F3 — see `START_HERE.txt`), and how to connect —
launch via `flyffbot.bat` (persistent profile, log in once) OR the dev harness
(`host.py` + `--remote-debugging-port=9222`).

## 4. Current state & next action
Status: **a multi-mode bot suite** — grind, heal, heal+buff (Ringmaster), ShopScanner — with a
tabbed per-script control panel (Status/EXP/Shop dashboards + calibration), a one-click installer,
and a multi-instance launcher. The healer/buffer and shop scanner work; grind works well on **clean
mobs** (aibats: distinct colour, dense, close). **The open vision problem is engage ACCURACY** on
harder cases (small/spread/low-contrast mobs): the select/attack clicks land off-mob too often (an
off-mob left-click = a Flyff MOVE = the character walks). Loop **speed** was substantially improved
(2026-06-28: faster search/fight-end/engage — see `CLAUDE.md` Status). See `CLAUDE.md` Status for the
full state and recent fixes (aggro fast-path, periodic recalibrate, idle "only turn when no mobs
visible" gate, the OCR-perf fix, the kill→next-mob speed tuning + ⚙️ Advanced panel settings).

**Run it (normal use):** `flyffbot.bat` → a window opens, log into the account, then use the
**control panel window**: pick a sub-tab (**Grind local / Heal+Buff / Heal / Grind selected /
Grind mem / ShopScanner / Calibrate**) and press **▶ Start** (■ Stop / ✨ Quick buff as needed).
`flyffbot.bat <name>` starts another instance (separate profile + window); several run at once.
First-time user on a fresh PC: **`install.bat`** then `flyffbot.bat` (see `START_HERE.txt`).

**Dev harness:** `python host.py` (bg) → log in → `python -u control.py` (attaches
via CDP, same panel). Probe scripts use `session.py`. **Hot-reload:** editing
`grind_local.py` (and `grind_local_2.py`) applies on the next panel-button click;
editing `control.py` needs a restart.

**(2026-07-02e) hoverScanSpeedup:** the default `cursor`-mode mob finder (hover + read the game's CSS
cursor) got faster/more reliable, all in `grind_local.py` (hot-reload, no restart): rAF-synced cursor
reads (no fixed sleep), a memory-based select-confirm, a memory-guided TARGETED spiral hover that
projects each nearby wanted mob's world position to a screen point (a passively self-calibrating
world↔screen fit, fed by the clicks the bot already makes — no dedicated calibration walk), and a
batched blind-scan fallback with an in-page cursor-swap logger. See `hoverScanSpeedup_PLAN.md` and
CLAUDE.md Status (2026-07-02e) for the full design + new tunables.

Don't re-derive (verified): attack = **two left-clicks** (select, then attack),
auto to death, **never click mid-fight** (pure-hold); detection primary = **mob name
tags by colour** (`find_nameplate_mobs`): light **yellow/beige** (normal) or **red**
(aggro) — **white/green = PLAYER tags, deliberately NOT matched**; attack point =
**selection-arrows center** (backed up by re-detecting the mob body — `engage`/`_attack_point`);
`calibrate_view` = max zoom-out → zoom-in (`ZOOM_IN_STEPS`) → top-down, and it now also runs on a
**timer** (`CALIBRATE_EVERY`); aggro mobs pop a white+orange **alert bubble** → click + attack `0`
(`find_aggro_symbol`); keep game menu/chat CLOSED (bright UI text = false tags).

**Where we are (M6 — the current focus):** refining the vision bot `grind_local.py`
directly. Recent rework of `engage()` added body-blob click refinement
(`refine_click_point`), selection verification + retry, and a relocated attack click
(`_attack_point`) — but it **still misclicks**. Next: try the `calibrate_view` zoom-in
(bigger mobs), re-tune `NAMEPLATE_DROP`/ROI, tighten the cyan-arrow threshold, and
curb post-miss walking. Then: safety stops (flee/logout at critical HP); jitter;
`assist` mode.

**Tried & parked — don't redo without reason:**
- **M7 memory-reading sensing / the `grind_local_2` HYBRID:** reading mob ids + world
  positions from WASM memory is ROCK-SOLID. The old vision-click hybrid needed a
  world→screen projection that drifted — but **that whole blocker is now GONE** (see the
  movement breakthrough below): we move server-side via packet rewrite, no projection needed.

**BREAKTHROUGH (2026-07-01, M8e) — vision-free MOVEMENT works; grind_RE is now viable:**
- **~~"crafted packets do NOT drive the character"~~ was WRONG** (it was only ever tested by
  checking the CLIENT, never the server). We now walk the char to ANY chosen world (x,y,z),
  **server-accepted**, with NO click-projection: fire a tiny throwaway trigger-click (the game
  emits a real move packet), and in a `ws.send` hook **rewrite that packet's DEST field to our
  chosen xyz + recompute the CRC** → the SERVER walks the char there. The 8-byte tail is
  `obj+48 ^ const` (not a MAC), so keeping the game's own tail + changing dest is accepted.
  Small hops (≤20u) are accepted ~instantly; implemented in **`re_sense.move_to_server(x,y,z)`**.
- **Panel:** RE/Memory tab has a per-mob **"go to"** button + **click-the-minimap-to-walk-there**
  (`update_mem_action` → `move_to_server`). Fast (2–5 s) + holds, no rubber-band.
- **Still OPEN — the ATTACK.** `wasmTable.get(1782)()` (memory-attack, func 4148) went STALE after
  a game patch (red markers, zero damage even in-range). Fable's plan (next task): frame-diff a real
  click-attack vs the call to see stale-index-vs-mechanism-change; verify `controller+2656` still
  holds the target (offsets may have moved); then do **attack via packet capture-and-rewrite** (the
  patch-stable layer), likely emitting the server-side target-SELECT packet the memory-write path
  used to make implicit. See `reverseEngineeringWasm_Overview.md` (M8e/M8f).

**Network/WASM RE** (`reverseEngineeringWASM/`): WASM disassembled + split per-function +
mechanical call graph; outgoing **frame format + cipher fully solved** (func 2779 = XOR 32-byte key
@mem 338112 + CRC-32 @338144); **entity registry @362748** gives mob ids + world positions;
**controller = `*(mem 1230588)`** (selected target i64 @+2656). **ACTIVE data-collection: `packet_log.py`
→ `packet_logs/`** logs every WS frame + bot action per session, so you can correlate an action to
the encrypted frame it triggers by TIMING (the practical way to ID attack/heal/buff opcodes — see
CLAUDE.md "Packet logging for RE"). Start with
`reverseEngineeringWASM/reverseEngineeringWasm_Overview.md` (plain-English status), then
`reverseEngineeringWASM/wasm_overview.md` (technical per-function lookup + memory map)
and `ReverseEngineerWasm_PLAN.md` (strategy). (The old `wstest.py` ⚡ WS-test experiment
has been removed.)

## 5. Architecture you must preserve
```
BotRunner loop → always-on safety (panic key, critical-HP) + active Mode.tick(ctx)
ctx = { Sensor, Actuator, Config }
Sensor   screenshot, hp_fraction, find_mobs, [later] find_player/party_hp/buffs
Actuator press, key_combo, click, ctrl_click   (all trusted via Playwright)
Config   keybinds (food="1"), thresholds, cooldowns, per-mode params (one file)
Modes    grind (now); assist/ringmaster (later) — new Mode subclass, no core rewrite
```

## 6. Working agreement
- All tunables in the Config file; test incrementally against the live game.
- Don't build a big framework before M0 passes.
- After meaningful progress, **update the Status lines** here and in `CLAUDE.md`,
  and the roadmap in `PROJECT_OVERVIEW.md`.
