chore: initialize repository baseline

Import the existing Electron + React + TypeScript app as the version-control
baseline before the scanner rework (C# input/capture sidecar, resolution-anchored
layout profiles, OCR preprocessing, eval harness, rescan-merge, GOOD interop).

Housekeeping in this commit:
- Remove orphaned temp_inputhelper_block.ts (duplicate of the input-helper script).
- Ignore .claude/scheduled_tasks.lock local session state.
- Add .gitattributes to normalize line endings (LF in repo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-07-05 20:31:01 +02:00
commit e76d88e0c7
147 changed files with 26220 additions and 0 deletions
@@ -0,0 +1,294 @@
import { automationBlockReason, requiresAdminForAutomation } from "../../../lib/automationPlanner";
import { captureRejectionReason } from "../../../lib/scannerCaptureQuality";
import { runAutoScanLoop } from "../../../lib/autoScanLoop";
import { clampScanLimit, emptyAutoScanStats, resolveScanTargetCount, type AutoScanStats, type ScanSummary } from "../../../lib/scannerSession";
import { getAutoReviewReason, wait } from "../../../lib/scanReviewUtils";
import type { AutomationRepositoryPort, RuntimeRepositoryPort } from "../../../infrastructure/repositories/rendererBridgeRepositories";
import type { AutomationGuard, BooleanResult, CaptureOptions, CaptureResult, ClickResult, RuntimeInfo, ScrollResult } from "../../../types/global";
import type { ParsedArtifactCandidate } from "../../../lib/artifactOcrParser";
import type { MutableRefObject } from "react";
import type { Dispatch, SetStateAction } from "react";
export interface ScanActionContext {
autoScanRunning: boolean;
setAutoScanRunning: Dispatch<SetStateAction<boolean>>;
isScanning: boolean;
stopVisibleScanRef: MutableRefObject<boolean>;
selectedSourceId: string;
bridgeReady: boolean;
automationRepo?: AutomationRepositoryPort;
runtimeRepo?: RuntimeRepositoryPort;
runtimeInfo?: RuntimeInfo | null;
scanLimit: number;
skipRows: number;
detectedInventoryCount: number;
setScanSummary: Dispatch<SetStateAction<ScanSummary | null>>;
setAutoScanStats: Dispatch<SetStateAction<AutoScanStats>>;
setReviewStatus: Dispatch<SetStateAction<string>>;
appendAutomationLog: (line: string) => void;
appendClickDiagnostics: (result: ClickResult, prefix?: string) => void;
captureSelectedSource: (delayMs?: number, focusGenshin?: boolean, options?: CaptureOptions) => Promise<CaptureResult | null>;
captureFastSelectedSource: (delayMs?: number, focusGenshin?: boolean) => Promise<CaptureResult | null>;
parseArtifact: (capture: CaptureResult | null) => ParsedArtifactCandidate | null;
persistParsedArtifact: (capture: CaptureResult | null, parsed: ParsedArtifactCandidate, source: string, needsReview: boolean) => Promise<boolean>;
saveReviewSample: (capture: CaptureResult | null, parsed: ParsedArtifactCandidate | null, reason?: string) => Promise<BooleanResult | null>;
shouldFlagArtifactForReview: (parsed: ParsedArtifactCandidate | null) => boolean;
focusDashboard: () => Promise<void>;
}
function buildScanSignature(parsed: ParsedArtifactCandidate) {
return `${parsed.name}-${parsed.slot}-${parsed.mainStat}-${parsed.mainValue}-${parsed.level ?? 0}`;
}
export async function runAutoReviewScan(context: ScanActionContext): Promise<void> {
const {
autoScanRunning,
selectedSourceId,
stopVisibleScanRef,
setAutoScanRunning,
setReviewStatus,
setScanSummary,
setAutoScanStats,
appendAutomationLog,
scanLimit,
detectedInventoryCount,
captureSelectedSource,
parseArtifact,
persistParsedArtifact,
saveReviewSample,
shouldFlagArtifactForReview,
focusDashboard,
} = context;
if (autoScanRunning || !selectedSourceId) return;
setAutoScanRunning(true);
stopVisibleScanRef.current = false;
setScanSummary(null);
setAutoScanStats(emptyAutoScanStats);
setReviewStatus("Manueller Scan laeuft. Klicke in Genshin auf ein anderes Artifact; nur neue Artifacts werden verarbeitet.");
const seen = new Set<string>();
const stats: AutoScanStats = { ...emptyAutoScanStats, pages: 1 };
let idleTicks = 0;
const maxArtifacts = resolveScanTargetCount(scanLimit, detectedInventoryCount);
const maxIdleTicks = 90;
while (!stopVisibleScanRef.current && stats.parsed < maxArtifacts && idleTicks < maxIdleTicks) {
const capture = await captureSelectedSource(0, true);
const rejection = captureRejectionReason(capture, parseArtifact(capture));
const parsed = parseArtifact(capture);
if (!capture || !parsed || rejection) {
if (capture && rejection) {
await saveReviewSample(capture, parsed, `manual:capture-rejected`);
stats.review++;
setAutoScanStats({ ...stats });
}
idleTicks++;
setReviewStatus(`Manueller Scan wartet auf ein lesbares Artifact... (${stats.parsed}/${maxArtifacts})${rejection ? ` ${rejection}` : ""}`);
await wait(700);
continue;
}
const signature = buildScanSignature(parsed);
if (seen.has(signature)) {
idleTicks++;
setReviewStatus(`Manueller Scan wartet auf ein neues Artifact... (${stats.parsed}/${maxArtifacts})`);
await wait(700);
continue;
}
seen.add(signature);
idleTicks = 0;
stats.attempted++;
stats.verified++;
stats.parsed++;
const reason = getAutoReviewReason(capture, parsed);
const needsReview = shouldFlagArtifactForReview(parsed);
if (reason) {
await saveReviewSample(capture, parsed, `manual:${reason}`);
stats.review++;
}
if (await persistParsedArtifact(capture, parsed, "manual-scan", needsReview)) {
stats.stored++;
}
setAutoScanStats({ ...stats });
setReviewStatus(`Manueller Scan: neues Artifact erkannt (${stats.parsed}/${maxArtifacts}). Klicke das naechste Artifact an oder druecke Stop.`);
await wait(700);
}
setAutoScanRunning(false);
const status: ScanSummary["status"] = stopVisibleScanRef.current ? "stopped" : "done";
const idleSuffix = idleTicks >= maxIdleTicks ? " Keine neuen Artifacts erkannt; manueller Scan beendet." : "";
await focusDashboard();
setReviewStatus(
`Manueller Scan ${status}. ${stats.verified} neue Ansichten verifiziert, ${stats.parsed} Artifacts gelesen, ${stats.stored} in der Datenbank gespeichert, ${stats.review} Review-Samples.${idleSuffix}`,
);
setScanSummary({
mode: "Manueller Scan",
status,
...stats,
targetCount: maxArtifacts,
gridLabel: "Nur neue, vom User angeklickte Artifacts wurden verarbeitet.",
});
appendAutomationLog(`manual scan finished: ${stats.parsed} parsed, ${stats.stored} stored, ${stats.review} review`);
}
export async function runVisibleGridScan(context: ScanActionContext): Promise<void> {
const {
autoScanRunning,
bridgeReady,
selectedSourceId,
runtimeInfo,
automationRepo,
runtimeRepo,
stopVisibleScanRef,
setAutoScanRunning,
setScanSummary,
setAutoScanStats,
setReviewStatus,
appendAutomationLog,
appendClickDiagnostics,
captureSelectedSource,
captureFastSelectedSource,
parseArtifact,
persistParsedArtifact,
saveReviewSample,
shouldFlagArtifactForReview,
scanLimit,
skipRows,
detectedInventoryCount,
focusDashboard,
} = context;
if (autoScanRunning || !bridgeReady || !selectedSourceId || !automationRepo?.clickScreen || !automationRepo?.scrollScreen) return;
const requiresAdminForAutoScan = requiresAdminForAutomation(runtimeInfo);
if (requiresAdminForAutoScan) {
setReviewStatus(
"App laeuft nicht als Administrator. Bitte die App schliessen und als Administrator neu starten - Auto-Scan braucht Administrator-Rechte, damit Windows die simulierten Eingaben an Genshin nicht blockiert.",
);
setScanSummary({
mode: "Automatischer Scan",
status: "blocked",
...emptyAutoScanStats,
targetCount: resolveScanTargetCount(scanLimit, detectedInventoryCount),
gridLabel: "App laeuft nicht als Administrator. Neustart als Administrator noetig.",
});
return;
}
setAutoScanRunning(true);
stopVisibleScanRef.current = false;
setScanSummary(null);
setAutoScanStats(emptyAutoScanStats);
setReviewStatus("Automatischer Scan startet. ESC gedrueckt halten oder Stop klicken beendet den Scan sofort.");
const freshRuntime = await runtimeRepo?.getRuntimeInfo().catch(() => null);
const adminBlockReason = automationBlockReason(freshRuntime);
if (adminBlockReason) {
setAutoScanRunning(false);
setReviewStatus(adminBlockReason);
appendAutomationLog("blocked: App laeuft nicht als Administrator, keine In-Game-Klicks ausgefuehrt");
setScanSummary({
mode: "Automatischer Scan",
status: "blocked",
...emptyAutoScanStats,
targetCount: resolveScanTargetCount(scanLimit, detectedInventoryCount),
gridLabel: adminBlockReason,
});
return;
}
if (freshRuntime) {
const required = freshRuntime.genshinFound ? `found:${freshRuntime.targetProcess || "genshin"}` : "not-found";
appendAutomationLog(`runtime ping: elevated=${freshRuntime.isElevated} ${required}`);
}
setReviewStatus("Genshin wird in den Vordergrund geholt...");
const focusResult = await automationRepo?.focusGenshin().catch(() => null);
if (focusResult) {
appendAutomationLog(
`focus: ${focusResult.focused ? "ok" : "fehlgeschlagen"} found:${focusResult.genshinFound ? "yes" : "no"} setForeground:${focusResult.setForegroundResult ?? "n/a"} target:${focusResult.targetProcess || "?"} fg:${focusResult.foregroundProcess || "?"}`,
);
}
if (!focusResult?.focused) {
setAutoScanRunning(false);
const reason = !focusResult?.genshinFound
? "Genshin-Prozess wurde nicht gefunden. Bitte pruefen, ob Genshin laeuft, und Auto-Scan erneut starten."
: "Genshin konnte nicht in den Vordergrund geholt werden. Bitte Genshin manuell anklicken/fokussieren und Auto-Scan erneut starten.";
setReviewStatus(reason);
setScanSummary({
mode: "Automatischer Scan",
status: "blocked",
...emptyAutoScanStats,
targetCount: resolveScanTargetCount(scanLimit, detectedInventoryCount),
gridLabel: reason,
});
return;
}
setReviewStatus("Genshin ist im Vordergrund. Automatischer Scan startet. ESC gedrueckt halten oder Stop klicken beendet den Scan sofort.");
const result = await runAutoScanLoop(
{
api: {
clickScreen: (x: number, y: number) => {
if (!automationRepo?.clickScreen) {
return Promise.resolve<ClickResult>({
ok: false,
x: 0,
y: 0,
clicked: false,
moved: false,
focused: false,
inputBlocked: false,
});
}
return automationRepo.clickScreen(x, y);
},
scrollScreen: (notches: number, anchorX?: number, anchorY?: number) => {
if (!automationRepo?.scrollScreen) {
return Promise.resolve<ScrollResult>({ ok: false, notchesSent: 0, inputBlocked: false });
}
return automationRepo.scrollScreen(notches, anchorX, anchorY);
},
getAutomationGuard: () =>
automationRepo?.getAutomationGuard?.() ??
Promise.resolve<AutomationGuard>({ ok: false, escapePressed: false, enterPressed: false, f9Pressed: false }),
},
captureSelectedSource: (delayMs = 0, focusGenshin = false) => captureSelectedSource(delayMs, focusGenshin),
captureFastSelectedSource,
parseArtifact,
persistParsedArtifact,
saveReviewSample,
getAutoReviewReason,
shouldFlagArtifactForReview,
appendAutomationLog,
appendClickDiagnostics,
setReviewStatus,
setAutoScanStats,
shouldStop: () => stopVisibleScanRef.current,
},
{
scanLimit,
skipRows,
detectedInventoryCount,
},
);
setAutoScanRunning(false);
if (result.blockedReason) appendAutomationLog(`stop: ${result.blockedReason}`);
await focusDashboard();
setReviewStatus(`Automatischer Scan ${result.status === "stopped" ? "gestoppt" : result.status === "blocked" ? "blockiert" : "fertig"}. ${result.stats.clicked} Klicks, ${result.stats.attempted} Positionen bearbeitet, ${result.stats.verified} Ansichten verifiziert, ${result.stats.parsed} gelesen, ${result.stats.stored} in der Datenbank, ${result.stats.review} Review-Samples, ${result.stats.duplicates} Duplikate, ${result.stats.misses} Misses.${result.blockedReason ? ` ${result.blockedReason}` : ""}`);
setScanSummary({
mode: "Automatischer Scan",
status: result.status,
...result.stats,
targetCount: result.targetCount,
gridLabel: result.gridLabel,
});
}