tooltip bounds
This commit is contained in:
parent
930e00c9cc
commit
bb2b9cf507
7 changed files with 474 additions and 56 deletions
|
|
@ -2,6 +2,7 @@ import express from 'express';
|
|||
import http from 'http';
|
||||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import path from 'path';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { logger } from '../util/logger.js';
|
||||
import { sleep } from '../util/sleep.js';
|
||||
|
|
@ -275,21 +276,47 @@ export class DashboardServer {
|
|||
...matches.map(m => ({ row: m.row, col: m.col, label: `MATCH ${(m.similarity * 100).toFixed(0)}%` })),
|
||||
];
|
||||
|
||||
// Focus game and hover each cell
|
||||
// Focus game, take one snapshot with mouse on empty space
|
||||
await this.debug.gameController.focusGame();
|
||||
await mkdir('items', { recursive: true });
|
||||
const tooltips: Array<{ row: number; col: number; label: string; text: string }> = [];
|
||||
const ts = Date.now();
|
||||
const reg = result.layout.region;
|
||||
const cellW = reg.width / result.layout.cols;
|
||||
const cellH = reg.height / result.layout.rows;
|
||||
|
||||
// Move mouse to empty space and take a single reference snapshot
|
||||
this.debug.gameController.moveMouseInstant(reg.x + reg.width + 50, reg.y + reg.height / 2);
|
||||
await sleep(50);
|
||||
await this.debug.screenReader.snapshot();
|
||||
await this.debug.screenReader.saveScreenshot(`items/${ts}_snapshot.png`);
|
||||
await sleep(200); // Let game settle before first hover
|
||||
|
||||
for (const cell of hoverCells) {
|
||||
const center = result.layout.region;
|
||||
const cellW = center.width / result.layout.cols;
|
||||
const cellH = center.height / result.layout.rows;
|
||||
const x = Math.round(center.x + cell.col * cellW + cellW / 2);
|
||||
const y = Math.round(center.y + cell.row * cellH + cellH / 2);
|
||||
this.broadcastLog('info', `Hovering ${cell.label} (${cell.row},${cell.col}) at (${x},${y})...`);
|
||||
await this.debug.gameController.moveMouseTo(x, y);
|
||||
await sleep(1000);
|
||||
const cellStart = performance.now();
|
||||
const x = Math.round(reg.x + cell.col * cellW + cellW / 2);
|
||||
const y = Math.round(reg.y + cell.row * cellH + cellH / 2);
|
||||
|
||||
// Quick Bézier move to the cell — tooltip appears on hover
|
||||
await this.debug.gameController.moveMouseFast(x, y);
|
||||
await sleep(50);
|
||||
const afterMove = performance.now();
|
||||
|
||||
// Diff-OCR: finds tooltip by row/column density of darkened pixels
|
||||
const imgPath = `items/${ts}_${cell.row}-${cell.col}.png`;
|
||||
const diff = await this.debug.screenReader.diffOcr(imgPath);
|
||||
const afterOcr = performance.now();
|
||||
const text = diff.text.trim();
|
||||
|
||||
const regionInfo = diff.region ? ` at (${diff.region.x},${diff.region.y}) ${diff.region.width}x${diff.region.height}` : '';
|
||||
tooltips.push({ row: cell.row, col: cell.col, label: cell.label, text });
|
||||
|
||||
this.broadcastLog('info',
|
||||
`${cell.label} (${cell.row},${cell.col}) [move: ${(afterMove - cellStart).toFixed(0)}ms, ocr: ${(afterOcr - afterMove).toFixed(0)}ms, total: ${(afterOcr - cellStart).toFixed(0)}ms]${regionInfo}: ${text.substring(0, 150)}${text.length > 150 ? '...' : ''}`);
|
||||
}
|
||||
|
||||
this.broadcastLog('info', `Done — hovered ${hoverCells.length} cells`);
|
||||
res.json({ ok: true, itemSize, matchCount: matches.length, hoveredCount: hoverCells.length });
|
||||
this.broadcastLog('info', `Done — hovered ${hoverCells.length} cells, read ${tooltips.filter(t => t.text).length} tooltips`);
|
||||
res.json({ ok: true, itemSize, matchCount: matches.length, hoveredCount: hoverCells.length, tooltips });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Debug test-match-hover failed');
|
||||
res.status(500).json({ error: 'Test match hover failed' });
|
||||
|
|
|
|||
|
|
@ -91,6 +91,14 @@ export class GameController {
|
|||
await this.inputSender.moveMouse(x, y);
|
||||
}
|
||||
|
||||
moveMouseInstant(x: number, y: number): void {
|
||||
this.inputSender.moveMouseInstant(x, y);
|
||||
}
|
||||
|
||||
async moveMouseFast(x: number, y: number): Promise<void> {
|
||||
await this.inputSender.moveMouseFast(x, y);
|
||||
}
|
||||
|
||||
async leftClickAt(x: number, y: number): Promise<void> {
|
||||
await this.inputSender.leftClick(x, y);
|
||||
}
|
||||
|
|
@ -99,6 +107,14 @@ export class GameController {
|
|||
await this.inputSender.rightClick(x, y);
|
||||
}
|
||||
|
||||
async holdAlt(): Promise<void> {
|
||||
await this.inputSender.keyDown(VK.MENU);
|
||||
}
|
||||
|
||||
async releaseAlt(): Promise<void> {
|
||||
await this.inputSender.keyUp(VK.MENU);
|
||||
}
|
||||
|
||||
async pressEscape(): Promise<void> {
|
||||
await this.inputSender.pressKey(VK.ESCAPE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,6 +222,46 @@ export class InputSender {
|
|||
await randomDelay(5, 15);
|
||||
}
|
||||
|
||||
moveMouseInstant(x: number, y: number): void {
|
||||
this.moveMouseRaw(x, y);
|
||||
}
|
||||
|
||||
/** Quick Bézier move — ~10-15ms, 5 steps, no jitter. Fast but not a raw teleport. */
|
||||
async moveMouseFast(x: number, y: number): Promise<void> {
|
||||
const start = this.getCursorPos();
|
||||
const end: Point = { x, y };
|
||||
const dx = end.x - start.x;
|
||||
const dy = end.y - start.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 10) {
|
||||
this.moveMouseRaw(x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
const perpX = -dy / distance;
|
||||
const perpY = dx / distance;
|
||||
const spread = distance * 0.15;
|
||||
|
||||
const cp1: Point = {
|
||||
x: start.x + dx * 0.3 + perpX * (Math.random() - 0.5) * spread,
|
||||
y: start.y + dy * 0.3 + perpY * (Math.random() - 0.5) * spread,
|
||||
};
|
||||
const cp2: Point = {
|
||||
x: start.x + dx * 0.7 + perpX * (Math.random() - 0.5) * spread,
|
||||
y: start.y + dy * 0.7 + perpY * (Math.random() - 0.5) * spread,
|
||||
};
|
||||
|
||||
const steps = 5;
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
const t = easeInOutQuad(i / steps);
|
||||
const pt = cubicBezier(t, start, cp1, cp2, end);
|
||||
this.moveMouseRaw(Math.round(pt.x), Math.round(pt.y));
|
||||
await sleep(2);
|
||||
}
|
||||
this.moveMouseRaw(x, y);
|
||||
}
|
||||
|
||||
async leftClick(x: number, y: number): Promise<void> {
|
||||
await this.moveMouse(x, y);
|
||||
await randomDelay(20, 50);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ export interface GridScanResult {
|
|||
matches?: GridMatch[];
|
||||
}
|
||||
|
||||
export interface DiffOcrResponse {
|
||||
text: string;
|
||||
lines: OcrLine[];
|
||||
region?: Region;
|
||||
}
|
||||
|
||||
export interface DetectGridResult {
|
||||
detected: boolean;
|
||||
region?: Region;
|
||||
|
|
@ -151,6 +157,22 @@ export class OcrDaemon {
|
|||
};
|
||||
}
|
||||
|
||||
async snapshot(): Promise<void> {
|
||||
await this.sendWithRetry({ cmd: 'snapshot' }, REQUEST_TIMEOUT);
|
||||
}
|
||||
|
||||
async diffOcr(savePath?: string, region?: Region): Promise<DiffOcrResponse> {
|
||||
const req: DaemonRequest = { cmd: 'diff-ocr' };
|
||||
if (savePath) req.path = savePath;
|
||||
if (region) req.region = region;
|
||||
const resp = await this.sendWithRetry(req, REQUEST_TIMEOUT);
|
||||
return {
|
||||
text: resp.text ?? '',
|
||||
lines: resp.lines ?? [],
|
||||
region: resp.region,
|
||||
};
|
||||
}
|
||||
|
||||
async saveScreenshot(path: string, region?: Region): Promise<void> {
|
||||
const req: DaemonRequest = { cmd: 'screenshot', path };
|
||||
if (region) req.region = region;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { mkdir } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { logger } from '../util/logger.js';
|
||||
import { OcrDaemon, type OcrResponse } from './OcrDaemon.js';
|
||||
import { OcrDaemon, type OcrResponse, type DiffOcrResponse } from './OcrDaemon.js';
|
||||
import { GridReader, type GridLayout, type CellCoord } from './GridReader.js';
|
||||
import type { Region } from '../types.js';
|
||||
|
||||
|
|
@ -102,6 +102,16 @@ export class ScreenReader {
|
|||
return pos !== null;
|
||||
}
|
||||
|
||||
// ── Snapshot / Diff-OCR (for tooltip reading) ──────────────────────
|
||||
|
||||
async snapshot(): Promise<void> {
|
||||
await this.daemon.snapshot();
|
||||
}
|
||||
|
||||
async diffOcr(savePath?: string, region?: Region): Promise<DiffOcrResponse> {
|
||||
return this.daemon.diffOcr(savePath, region);
|
||||
}
|
||||
|
||||
// ── Save utilities ──────────────────────────────────────────────────
|
||||
|
||||
async saveScreenshot(path: string): Promise<void> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue