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,233 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const genshin = require('genshin-db');
|
||||
|
||||
const opts = { matchCategories: true, verboseCategories: true, resultLanguage: 'English', queryLanguages: ['English'] };
|
||||
|
||||
function asArray(value) {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
const characters = asArray(genshin.characters('names', opts))
|
||||
.map((entry) => ({
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
rarity: entry.rarity,
|
||||
element: entry.elementText,
|
||||
weapon: entry.weaponText,
|
||||
}))
|
||||
.filter((entry) => entry.name)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const artifacts = asArray(genshin.artifacts('names', opts))
|
||||
.map((entry) => ({
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
rarityList: entry.rarityList ?? [],
|
||||
pieces: [entry.flower, entry.plume, entry.sands, entry.goblet, entry.circlet]
|
||||
.filter(Boolean)
|
||||
.map((piece) => ({ name: piece.name, relicType: piece.relicType })),
|
||||
}))
|
||||
.filter((entry) => entry.name)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const artifactPieces = artifacts
|
||||
.flatMap((set) =>
|
||||
set.pieces.map((piece) => ({
|
||||
name: piece.name,
|
||||
setName: set.name,
|
||||
slot: slotFromRelicType(piece.relicType),
|
||||
relicType: piece.relicType,
|
||||
})),
|
||||
)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const slotByPiece = Object.fromEntries(artifactPieces.map((piece) => [piece.name, piece.slot]));
|
||||
const setByPiece = Object.fromEntries(artifactPieces.map((piece) => [piece.name, piece.setName]));
|
||||
|
||||
const mainStats = [
|
||||
'Elemental Mastery',
|
||||
'Energy Recharge',
|
||||
'CRIT Rate',
|
||||
'CRIT DMG',
|
||||
'Healing Bonus',
|
||||
'ATK%',
|
||||
'HP%',
|
||||
'DEF%',
|
||||
'ATK',
|
||||
'HP',
|
||||
'DEF',
|
||||
'Hydro DMG Bonus',
|
||||
'Pyro DMG Bonus',
|
||||
'Electro DMG Bonus',
|
||||
'Cryo DMG Bonus',
|
||||
'Dendro DMG Bonus',
|
||||
'Anemo DMG Bonus',
|
||||
'Geo DMG Bonus',
|
||||
'Physical DMG Bonus',
|
||||
];
|
||||
|
||||
const substats = ['CRIT DMG', 'CRIT Rate', 'Energy Recharge', 'Elemental Mastery', 'ATK', 'ATK%', 'HP', 'HP%', 'DEF', 'DEF%'];
|
||||
const 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 mainStatValueReferences = {
|
||||
'Flower of Life': [{ stat: 'HP', base: 717, max: 4780 }],
|
||||
'Plume of Death': [{ stat: 'ATK', base: 47, max: 311 }],
|
||||
'Sands of Eon': [
|
||||
{ stat: 'HP%', base: 7.0, max: 46.6 },
|
||||
{ stat: 'ATK%', base: 7.0, max: 46.6 },
|
||||
{ stat: 'DEF%', base: 8.7, max: 58.3 },
|
||||
{ stat: 'Energy Recharge', base: 7.8, max: 51.8 },
|
||||
{ stat: 'Elemental Mastery', base: 28, max: 187 },
|
||||
],
|
||||
'Goblet of Eonothem': [
|
||||
{ stat: 'HP%', base: 7.0, max: 46.6 },
|
||||
{ stat: 'ATK%', base: 7.0, max: 46.6 },
|
||||
{ stat: 'DEF%', base: 8.7, max: 58.3 },
|
||||
{ stat: 'Elemental Mastery', base: 28, max: 187 },
|
||||
{ stat: 'Hydro DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Pyro DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Electro DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Cryo DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Dendro DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Anemo DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Geo DMG Bonus', base: 7.0, max: 46.6 },
|
||||
{ stat: 'Physical DMG Bonus', base: 8.7, max: 58.3 },
|
||||
],
|
||||
'Circlet of Logos': [
|
||||
{ stat: 'HP%', base: 7.0, max: 46.6 },
|
||||
{ stat: 'ATK%', base: 7.0, max: 46.6 },
|
||||
{ stat: 'DEF%', base: 8.7, max: 58.3 },
|
||||
{ stat: 'Elemental Mastery', base: 28, max: 187 },
|
||||
{ stat: 'CRIT Rate', base: 4.7, max: 31.1 },
|
||||
{ stat: 'CRIT DMG', base: 9.3, max: 62.2 },
|
||||
{ stat: 'Healing Bonus', base: 5.4, max: 35.9 },
|
||||
],
|
||||
};
|
||||
|
||||
const data = {
|
||||
schemaVersion: 2,
|
||||
generatedAt: new Date().toISOString(),
|
||||
source: {
|
||||
package: 'genshin-db',
|
||||
version: require('genshin-db/package.json').version,
|
||||
resultLanguage: opts.resultLanguage,
|
||||
},
|
||||
sourceVersions: {
|
||||
genshinDb: require('genshin-db/package.json').version,
|
||||
resultLanguage: opts.resultLanguage,
|
||||
},
|
||||
sourceVersion: `genshin-db@${require('genshin-db/package.json').version}`,
|
||||
characters,
|
||||
artifactSets: artifacts,
|
||||
artifactPieces,
|
||||
slotByPiece,
|
||||
setByPiece,
|
||||
slots: ['Flower of Life', 'Plume of Death', 'Sands of Eon', 'Goblet of Eonothem', 'Circlet of Logos'],
|
||||
mainStats,
|
||||
mainStatsBySlot,
|
||||
mainStatValueReferences,
|
||||
substats,
|
||||
stats: {
|
||||
main: mainStats,
|
||||
mainBySlot: mainStatsBySlot,
|
||||
sub: substats,
|
||||
},
|
||||
aliases: {
|
||||
stats: {
|
||||
'Crit Damage': 'CRIT DMG',
|
||||
'Critical Damage': 'CRIT DMG',
|
||||
'Crit Rate': 'CRIT Rate',
|
||||
'Critical Rate': 'CRIT Rate',
|
||||
'Energy Recharge %': 'Energy Recharge',
|
||||
'Elemental Master': 'Elemental Mastery',
|
||||
'Elemental Masterie': 'Elemental Mastery',
|
||||
'Heal Bonus': 'Healing Bonus',
|
||||
},
|
||||
textReplacements: {
|
||||
'CIT DMG': 'CRIT DMG',
|
||||
'CRIT DMG+I': 'CRIT DMG+1',
|
||||
'CRIT Rate+Z': 'CRIT Rate+2',
|
||||
'Energv Recharge': 'Energy Recharge',
|
||||
'Elemental Masterv': 'Elemental Mastery',
|
||||
'Equipped;': 'Equipped:',
|
||||
},
|
||||
slotAliases: {
|
||||
'Sands of Eon Vi': 'Sands of Eon',
|
||||
'Sands of Eon V': 'Sands of Eon',
|
||||
'Sands of Eon 2': 'Sands of Eon',
|
||||
'Flower of Life 2': 'Flower of Life',
|
||||
'Flower of Lif': 'Flower of Life',
|
||||
'Goblet of Eonotherm': 'Goblet of Eonothem',
|
||||
'Goblet of Eonothemn': 'Goblet of Eonothem',
|
||||
'Circlet of Logas': 'Circlet of Logos',
|
||||
'Circlet of Loges': 'Circlet of Logos',
|
||||
},
|
||||
setAliases: {
|
||||
'Viridescent Venere': 'Viridescent Venerer',
|
||||
'Maiden Beloved:': 'Maiden Beloved',
|
||||
'Gladiators Finale': "Gladiator's Finale",
|
||||
},
|
||||
pieceAliases: {
|
||||
'A Note in Springs Leich': "A Note in Spring's Leich",
|
||||
'Viridescent Vencrers Vessel': "Viridescent Venerer's Vessel",
|
||||
'Holy Crown of the Believer ': 'Holy Crown of the Believer',
|
||||
},
|
||||
characterAliases: {
|
||||
'Citlall': 'Citlali',
|
||||
'Sandrone ': 'Sandrone',
|
||||
'Qiqi ': 'Qiqi',
|
||||
},
|
||||
},
|
||||
uiProfiles: {
|
||||
artifactDetailEn: {
|
||||
language: 'English',
|
||||
supportedResolutions: ['1920x1080', '2560x1440', '3840x2160'],
|
||||
detailPanel: { x: 0.5, y: 0.05, width: 0.45, height: 0.9 },
|
||||
note: 'Relative profile used as scanner contract; runtime crops may tune offsets from review samples.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const outPath = path.join(process.cwd(), 'src', 'data', 'genshinGameData.json');
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||
fs.writeFileSync(outPath, JSON.stringify(data, null, 2) + '\n');
|
||||
console.log(`generated ${outPath}`);
|
||||
console.log(`${characters.length} characters, ${artifacts.length} artifact sets`);
|
||||
console.log(`${artifactPieces.length} artifact pieces, ${mainStats.length} main stats, ${substats.length} substats`);
|
||||
|
||||
function slotFromRelicType(relicType) {
|
||||
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