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:
@@ -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 "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user