Improve IK-style artifact scanner pipeline
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
textReplacements,
|
||||
} from "./genshinData.js";
|
||||
import { fuzzyFindKnown, simplifyForMatch } from "./fuzzyMatch.js";
|
||||
import { matchCharacter, matchPiece, matchSet, matchSlot, matchStat } from "./genshinLookup.js";
|
||||
import { implausibleSubstats } from "./substatRolls.js";
|
||||
|
||||
type MainStatValueReference = { stat: string; base: number; max: number };
|
||||
@@ -62,15 +63,22 @@ export function parseArtifactCandidate(capture: CaptureResult | null): ParsedArt
|
||||
(capture.ocr ?? []).map((entry: (typeof capture.ocr)[number]) => [entry.id, normalizeText(entry.text)]),
|
||||
);
|
||||
const allText = normalizeText((capture.ocr ?? []).map((entry: (typeof capture.ocr)[number]) => entry.text).join("\n"));
|
||||
const titleText = byId.get("artifact-title") ?? "";
|
||||
const mainText = byId.get("artifact-main-stat") ?? "";
|
||||
const nameText = byId.get("artifact-name") ?? "";
|
||||
const slotOnlyText = byId.get("artifact-slot") ?? "";
|
||||
const legacyTitleText = byId.get("artifact-title") ?? "";
|
||||
const titleText = [nameText, slotOnlyText].filter(Boolean).join("\n") || legacyTitleText;
|
||||
const mainLabelText = byId.get("artifact-main-stat-label") ?? "";
|
||||
const mainValueText = byId.get("artifact-main-stat-value") ?? "";
|
||||
const legacyMainText = byId.get("artifact-main-stat") ?? "";
|
||||
const mainText = [mainLabelText, mainValueText].filter(Boolean).join("\n") || legacyMainText;
|
||||
const levelText = byId.get("artifact-level") ?? "";
|
||||
const substatText = byId.get("artifact-substats") ?? "";
|
||||
const setText = byId.get("artifact-set-effects") ?? "";
|
||||
const footerText = byId.get("artifact-footer") ?? "";
|
||||
|
||||
const nameField = parseArtifactName(titleText);
|
||||
const slotField = parseSlot(titleText + "\n" + allText, nameField.value);
|
||||
const levelField = parseArtifactLevel(substatText + "\n" + mainText + "\n" + allText);
|
||||
const slotField = parseSlot([slotOnlyText, titleText, allText].filter(Boolean).join("\n"), nameField);
|
||||
const levelField = parseArtifactLevel([levelText, substatText, mainText, allText].filter(Boolean).join("\n"));
|
||||
const parsedLevel = levelField.value ? Number.parseInt(levelField.value, 10) : null;
|
||||
const level = parsedLevel ?? 0;
|
||||
let mainStatField = inferMainStat(slotField.value, mainText);
|
||||
@@ -100,7 +108,7 @@ export function parseArtifactCandidate(capture: CaptureResult | null): ParsedArt
|
||||
}
|
||||
const substats = parseSubstats([substatText, leadingSetEffectText(setText)].filter(Boolean).join("\n"));
|
||||
const substatsField = field(substats.join(", "), substats.length >= 4 ? 96 : substats.length >= 3 ? 82 : substats.length > 0 ? 55 : 0, substats.length ? "ocr" : "missing");
|
||||
const setField = parseSetName(setText, nameField.value);
|
||||
const setField = parseSetName(setText, nameField);
|
||||
const equippedField = parseEquippedCharacter(footerText + "\n" + allText);
|
||||
const notes: string[] = [];
|
||||
|
||||
@@ -161,6 +169,10 @@ function parseArtifactName(titleText: string): ParsedField {
|
||||
.filter(Boolean);
|
||||
|
||||
for (const line of titleLines) {
|
||||
const match = matchPiece(line);
|
||||
if (match.value) {
|
||||
return field(match.value, match.confidence, match.source === "exact" || match.source === "alias" ? "database" : "fallback");
|
||||
}
|
||||
const alias = normalizePieceAlias(line);
|
||||
if (alias) return field(alias, 96, "database");
|
||||
}
|
||||
@@ -172,13 +184,17 @@ function parseArtifactName(titleText: string): ParsedField {
|
||||
return fallback ? field(fallback, 50, "fallback") : field("", 0, "missing");
|
||||
}
|
||||
|
||||
function parseSlot(text: string, artifactName: string): ParsedField {
|
||||
function parseSlot(text: string, artifactName: ParsedField): ParsedField {
|
||||
const slotLines = text
|
||||
.split("\n")
|
||||
.map((line) => cleanupOcrLabel(line))
|
||||
.filter(Boolean);
|
||||
|
||||
for (const line of slotLines) {
|
||||
const match = matchSlot(line);
|
||||
if (match.value) {
|
||||
return field(match.value, match.confidence, match.source === "fuzzy" ? "fallback" : "ocr");
|
||||
}
|
||||
const alias = normalizeSlotAlias(line);
|
||||
if (alias) return field(alias, 96, "ocr");
|
||||
}
|
||||
@@ -186,18 +202,22 @@ function parseSlot(text: string, artifactName: string): ParsedField {
|
||||
const directSlot = fuzzyFindKnown(text, slotNames, 0.68);
|
||||
if (directSlot) return field(directSlot.value, Math.round(directSlot.score * 100), directSlot.score >= 0.95 ? "ocr" : "fallback");
|
||||
|
||||
const derivedSlot = artifactName ? pieceToSlot.get(artifactName) ?? "" : "";
|
||||
return derivedSlot ? field(derivedSlot, 94, "derived") : field("", 0, "missing");
|
||||
const derivedSlot = artifactName.value ? pieceToSlot.get(artifactName.value) ?? "" : "";
|
||||
return derivedSlot ? field(derivedSlot, derivedConfidence(artifactName, 94), "derived") : field("", 0, "missing");
|
||||
}
|
||||
|
||||
function parseSetName(setText: string, artifactName: string): ParsedField {
|
||||
const setFromPiece = artifactName ? pieceToSet.get(artifactName) : undefined;
|
||||
function parseSetName(setText: string, artifactName: ParsedField): ParsedField {
|
||||
const setFromPiece = artifactName.value ? pieceToSet.get(artifactName.value) : undefined;
|
||||
const candidateLines = setText
|
||||
.split("\n")
|
||||
.map((line) => line.trim().replace(/:$/, ""))
|
||||
.filter((line) => line.length > 3 && !/^\d/.test(line) && !/piece set/i.test(line));
|
||||
|
||||
for (const line of candidateLines) {
|
||||
const match = matchSet(line);
|
||||
if (match.value) {
|
||||
return field(match.value, match.confidence, match.source === "exact" || match.source === "alias" ? "database" : "fallback");
|
||||
}
|
||||
const alias = normalizeSetAlias(line);
|
||||
if (alias) return field(alias, 96, "database");
|
||||
}
|
||||
@@ -206,10 +226,15 @@ function parseSetName(setText: string, artifactName: string): ParsedField {
|
||||
|
||||
const setFromText = fuzzyFindKnown(`${directLine ?? ""}\n${setText}`, knownSets, 0.64);
|
||||
if (setFromText && (!setFromPiece || setFromText.score >= 0.78)) return field(setFromText.value, Math.round(setFromText.score * 100), setFromText.score >= 0.95 ? "ocr" : "fallback");
|
||||
if (setFromPiece) return field(setFromPiece, 92, "derived");
|
||||
if (setFromPiece) return field(setFromPiece, derivedConfidence(artifactName, 92), "derived");
|
||||
return setFromText ? field(setFromText.value, Math.round(setFromText.score * 100), "fallback") : field("", 0, "missing");
|
||||
}
|
||||
|
||||
function derivedConfidence(sourceField: ParsedField, maxConfidence: number) {
|
||||
if (sourceField.source === "database" || sourceField.confidence >= maxConfidence) return maxConfidence;
|
||||
return Math.max(45, Math.min(maxConfidence, sourceField.confidence));
|
||||
}
|
||||
|
||||
function parseArtifactLevel(text: string): ParsedField {
|
||||
const lines = normalizeText(text)
|
||||
.split("\n")
|
||||
@@ -294,6 +319,10 @@ function inferMainStat(slot: string, text: string): ParsedField {
|
||||
if (direct) return field(promotePercentVariant(direct, text), 94, "ocr");
|
||||
|
||||
const allowedForSlot = sortLongestFirst(allowedMainStatsForSlot(slot));
|
||||
const lookup = matchStat(text);
|
||||
if (lookup.value && allowedForSlot.includes(lookup.value)) {
|
||||
return field(promotePercentVariant(lookup.value, text), lookup.confidence, lookup.source === "fuzzy" ? "fallback" : "ocr");
|
||||
}
|
||||
const fuzzyAllowed = fuzzyFindKnown(text, allowedForSlot, 0.68);
|
||||
if (fuzzyAllowed) return field(promotePercentVariant(fuzzyAllowed.value, text), Math.round(fuzzyAllowed.score * 100), "fallback");
|
||||
|
||||
@@ -332,6 +361,7 @@ function findDirectMainStat(text: string) {
|
||||
if (hasPercentValue && /(^|\s)atk(\s|$)/i.test(text)) return "ATK%";
|
||||
if (hasPercentValue && /(^|\s)hp(\s|$)/i.test(text)) return "HP%";
|
||||
if (hasPercentValue && /(^|\s)def(\s|$)/i.test(text)) return "DEF%";
|
||||
if (hasPercentValue && /(^|\s)dlt(\s|$)/i.test(text)) return "DEF%";
|
||||
|
||||
return "";
|
||||
}
|
||||
@@ -461,6 +491,10 @@ function parseEquippedCharacter(text: string): ParsedField {
|
||||
if (!equippedLine) return field("Not detected", 45, "missing");
|
||||
|
||||
const afterLabel = cleanupCharacterNoise(equippedLine);
|
||||
const match = afterLabel ? matchCharacter(afterLabel) : null;
|
||||
if (match?.value) {
|
||||
return field(match.value, match.confidence, match.source === "fuzzy" ? "fallback" : "database");
|
||||
}
|
||||
const alias = afterLabel ? normalizeCharacterAlias(afterLabel) : "";
|
||||
if (alias) return field(alias, 96, "database");
|
||||
const known = afterLabel ? fuzzyFindKnown(afterLabel, knownCharacters, 0.6) : null;
|
||||
@@ -585,4 +619,3 @@ function sortLongestFirst(values: string[]) {
|
||||
function escapeRegex(value: string) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user