Document merchant HUD and location capture plan
This commit is contained in:
+73
-1
@@ -6,6 +6,7 @@ import {
|
||||
displayCaptureIssue,
|
||||
isCalibrationReady,
|
||||
isRectInside,
|
||||
merchantGroups,
|
||||
parseFieldValue,
|
||||
parseStoreHeaderText,
|
||||
parseTooltipText,
|
||||
@@ -19,10 +20,16 @@ import {
|
||||
findTextRunEnd,
|
||||
fitOcrTextRect,
|
||||
occupiedSlotScore,
|
||||
isColoredItemTextPixel,
|
||||
isWhiteTextPixel,
|
||||
isYellowTextPixel,
|
||||
} from "../src/vision.js";
|
||||
import { activateShop, recordSaleMiss, resetShopProgress } from "../src/frame-analyzer.js";
|
||||
import {
|
||||
activateShop,
|
||||
recordSaleMiss,
|
||||
resetShopProgress,
|
||||
shouldReadStoreHeader,
|
||||
} from "../src/frame-analyzer.js";
|
||||
|
||||
test("keeps white text pixels and removes colored or dark pixels", () => {
|
||||
assert.equal(isWhiteTextPixel(220, 215, 195), true);
|
||||
@@ -37,6 +44,13 @@ test("keeps yellow tooltip prices without accepting brown UI pixels", () => {
|
||||
assert.equal(isYellowTextPixel(190, 120, 40), false);
|
||||
});
|
||||
|
||||
test("keeps blue and purple item names without accepting brown UI pixels", () => {
|
||||
assert.equal(isColoredItemTextPixel(80, 145, 230), true);
|
||||
assert.equal(isColoredItemTextPixel(190, 90, 220), true);
|
||||
assert.equal(isColoredItemTextPixel(220, 215, 195), false);
|
||||
assert.equal(isColoredItemTextPixel(190, 120, 40), false);
|
||||
});
|
||||
|
||||
test("stops a tooltip text run before distant UI noise", () => {
|
||||
const columns = Array(48).fill(false);
|
||||
for (let index = 2; index <= 7; index += 1) columns[index] = true;
|
||||
@@ -130,6 +144,33 @@ test("fits a short row at the frame edge even when the saved width is wider", ()
|
||||
);
|
||||
});
|
||||
|
||||
test("fits colored item-name rows only when item colors are enabled", () => {
|
||||
const data = new Uint8ClampedArray(20 * 3 * 4);
|
||||
for (let column = 0; column < 4; column += 1) {
|
||||
const index = (20 + column) * 4;
|
||||
data[index] = 190;
|
||||
data[index + 1] = 90;
|
||||
data[index + 2] = 220;
|
||||
data[index + 3] = 255;
|
||||
}
|
||||
const canvas = {
|
||||
width: 40,
|
||||
height: 20,
|
||||
getContext() {
|
||||
return { getImageData: () => ({ data }) };
|
||||
},
|
||||
};
|
||||
const rect = { x: 20, y: 5, width: 50, height: 3 };
|
||||
|
||||
assert.equal(fitOcrTextRect(canvas, rect), null);
|
||||
assert.deepEqual(fitOcrTextRect(canvas, rect, { includeItemColors: true }), {
|
||||
x: 20,
|
||||
y: 5,
|
||||
width: 10,
|
||||
height: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps only letters, digits and spaces in OCR item names", () => {
|
||||
assert.equal(cleanItemName(" ~~ Soulshot: S-grade ` "), "Soulshot S grade");
|
||||
assert.equal(cleanItemName("Кристалл № 12"), "Кристалл 12");
|
||||
@@ -218,6 +259,32 @@ test("keeps transient grid images and slot state out of copied JSON", () => {
|
||||
assert.doesNotMatch(JSON.stringify(exported), /iconSlots|gridCaptured|thumbnail|internal/);
|
||||
});
|
||||
|
||||
test("builds one merchant board and keeps the latest price per item", () => {
|
||||
const groups = merchantGroups([
|
||||
{
|
||||
source: "L2 window 1",
|
||||
saleFound: true,
|
||||
side: "sell",
|
||||
merchant: "Dwa",
|
||||
items: [
|
||||
{ itemId: 1864, name: "Stem", side: "sell", merchant: "Dwa", priceAdena: 100, seenAt: "2026-08-12T10:00:00.000Z" },
|
||||
{ itemId: 1864, name: "Stem", side: "sell", merchant: "Dwa", priceAdena: 120, seenAt: "2026-08-12T10:01:00.000Z" },
|
||||
],
|
||||
},
|
||||
{
|
||||
source: "L2 window 2",
|
||||
saleFound: false,
|
||||
items: [{ itemId: 57, name: "Adena", side: "buy", merchant: "Gnumli", priceAdena: 1, seenAt: "2026-08-12T10:02:00.000Z" }],
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(groups.length, 2);
|
||||
assert.equal(groups[0].merchant, "Dwa");
|
||||
assert.equal(groups[0].active, true);
|
||||
assert.deepEqual(groups[0].items.map(({ priceAdena }) => priceAdena), [120]);
|
||||
assert.equal(groups[1].merchant, "Gnumli");
|
||||
});
|
||||
|
||||
test("resets only transient shop progress when a shop closes or changes", () => {
|
||||
const result = {
|
||||
activeShopKey: "sell:gnumli",
|
||||
@@ -264,6 +331,11 @@ test("keeps shop progress through two misses and clears it on the third", () =>
|
||||
assert.equal(result.gridCaptured, false);
|
||||
});
|
||||
|
||||
test("does not read a store header while a Price tooltip is visible", () => {
|
||||
assert.equal(shouldReadStoreHeader(true), false);
|
||||
assert.equal(shouldReadStoreHeader(false), true);
|
||||
});
|
||||
|
||||
test("parses a hovered item tooltip", () => {
|
||||
assert.deepEqual(
|
||||
parseTooltipText("Tears of Eva\nPrice : 3,000,000 Adena\n(3 Million Adena)"),
|
||||
|
||||
Reference in New Issue
Block a user