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
+164
View File
@@ -0,0 +1,164 @@
import gameData from "../data/genshinGameData.json" with { type: "json" };
import { simplifyForMatch } from "./fuzzyMatch.js";
type ArtifactPiece = {
name: string;
setName: string;
slot: string;
relicType: string;
};
type Character = {
name: string;
};
type GenshinGameDataContract = typeof gameData & {
artifactPieces?: ArtifactPiece[];
stats?: {
main?: string[];
mainBySlot?: Record<string, string[]>;
sub?: string[];
};
aliases?: {
stats?: Record<string, string>;
textReplacements?: Record<string, string>;
slotAliases?: Record<string, string>;
setAliases?: Record<string, string>;
pieceAliases?: Record<string, string>;
characterAliases?: Record<string, string>;
};
mainStatsBySlot?: Record<string, string[]>;
mainStatValueReferences?: Record<string, Array<{ stat: string; base: number; max: number }>>;
characters?: Character[];
};
export const genshinGameData = gameData as GenshinGameDataContract;
export const slotNames = genshinGameData.slots;
export const statAliases = genshinGameData.aliases?.stats ?? {};
export const textReplacements = genshinGameData.aliases?.textReplacements ?? {};
export const slotAliases = genshinGameData.aliases?.slotAliases ?? {};
export const setAliases = genshinGameData.aliases?.setAliases ?? {};
export const pieceAliases = genshinGameData.aliases?.pieceAliases ?? {};
export const characterAliases = genshinGameData.aliases?.characterAliases ?? {};
export const knownSets = genshinGameData.artifactSets.map((set) => set.name);
export const knownCharacters = (genshinGameData.characters ?? []).map((character) => character.name);
export const sourceVersion = genshinGameData.sourceVersion ?? "unknown";
export const fixedMainStatBySlot: Record<string, string> = {
"Flower of Life": "HP",
"Plume of Death": "ATK",
};
const bundledMainStats = genshinGameData.stats?.main ?? genshinGameData.mainStats;
const bundledSubstats = genshinGameData.stats?.sub ?? genshinGameData.substats;
export const mainStatsBySlot: Record<string, string[]> = genshinGameData.stats?.mainBySlot ?? genshinGameData.mainStatsBySlot ?? {
"Flower of Life": ["HP"],
"Plume of Death": ["ATK"],
"Sands of Eon": ["HP%", "ATK%", "DEF%", "Energy Recharge", "Elemental Mastery"],
"Goblet of Eonothem": [
"HP%",
"ATK%",
"DEF%",
"Elemental Mastery",
"Hydro DMG Bonus",
"Pyro DMG Bonus",
"Electro DMG Bonus",
"Cryo DMG Bonus",
"Dendro DMG Bonus",
"Anemo DMG Bonus",
"Geo DMG Bonus",
"Physical DMG Bonus",
],
"Circlet of Logos": ["HP%", "ATK%", "DEF%", "Elemental Mastery", "CRIT Rate", "CRIT DMG", "Healing Bonus"],
};
const fallbackArtifactPieces = genshinGameData.artifactSets.flatMap((artifactSet) =>
artifactSet.pieces.map((piece) => ({
name: piece.name,
setName: artifactSet.name,
slot: slotFromRelicType(piece.relicType),
relicType: piece.relicType,
})),
);
export const artifactPieces: ArtifactPiece[] = (genshinGameData.artifactPieces ?? fallbackArtifactPieces)
.filter((piece) => piece.name && piece.setName && piece.slot);
export const pieceToSet = new Map(artifactPieces.map((piece) => [piece.name, piece.setName]));
export const pieceToSlot = new Map(artifactPieces.map((piece) => [piece.name, piece.slot]));
export const knownPieceNames = artifactPieces.map((piece) => piece.name);
export const globalMainStats = unique([
...bundledMainStats,
...Object.keys(statAliases),
]);
export const globalSubstats = unique([
...bundledSubstats,
...Object.keys(statAliases),
]);
export const mainStatValueReferences = genshinGameData.mainStatValueReferences ?? {};
export function allowedMainStatsForSlot(slot: string) {
return mainStatsBySlot[slot] ?? globalMainStats;
}
export function canonicalStatName(raw: string) {
return statAliases[raw] ?? raw;
}
export function normalizeSlotAlias(raw: string) {
const simplified = simplifyForMatch(raw);
const aliasMatch = Object.entries(slotAliases).find(([alias]) => simplifyForMatch(alias) === simplified);
if (aliasMatch) return aliasMatch[1];
const direct = slotNames.find((slot) => simplifyForMatch(slot) === simplified);
return direct ?? "";
}
export function normalizeSetAlias(raw: string) {
const simplified = simplifyForMatch(raw);
const aliasMatch = Object.entries(setAliases).find(([alias]) => simplifyForMatch(alias) === simplified);
if (aliasMatch) return aliasMatch[1];
const direct = knownSets.find((set) => simplifyForMatch(set) === simplified);
return direct ?? "";
}
export function normalizePieceAlias(raw: string) {
const simplified = simplifyForMatch(raw);
const aliasMatch = Object.entries(pieceAliases).find(([alias]) => simplifyForMatch(alias) === simplified);
if (aliasMatch) return aliasMatch[1];
const direct = knownPieceNames.find((piece) => simplifyForMatch(piece) === simplified);
return direct ?? "";
}
export function normalizeCharacterAlias(raw: string) {
const simplified = simplifyForMatch(raw);
const aliasMatch = Object.entries(characterAliases).find(([alias]) => simplifyForMatch(alias) === simplified);
if (aliasMatch) return aliasMatch[1];
const direct = knownCharacters.find((character) => simplifyForMatch(character) === simplified);
return direct ?? "";
}
function unique(values: string[]) {
return [...new Set(values)];
}
function slotFromRelicType(relicType: string) {
switch (relicType) {
case "EQUIP_BRACER":
return "Flower of Life";
case "EQUIP_NECKLACE":
return "Plume of Death";
case "EQUIP_SHOES":
return "Sands of Eon";
case "EQUIP_RING":
return "Goblet of Eonothem";
case "EQUIP_DRESS":
return "Circlet of Logos";
default:
return "";
}
}