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,87 @@
|
||||
param(
|
||||
[string]$ProjectRoot
|
||||
)
|
||||
|
||||
# Mit -NoExit gestartet: dieses Fenster bleibt immer offen (siehe dev-admin.cmd),
|
||||
# aber ein sauberer try/catch mit klarer Meldung ist trotzdem besser als ein
|
||||
# roher Stacktrace, wenn z.B. der Dev-Port schon belegt ist.
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
try {
|
||||
$project = (Resolve-Path -LiteralPath $ProjectRoot).Path
|
||||
|
||||
Write-Host "Projekt: $project"
|
||||
|
||||
# A UAC-elevated process gets its environment rebuilt fresh from the
|
||||
# registry; it does NOT inherit PATH edits that only exist in the calling
|
||||
# (non-elevated) shell session (e.g. a version manager that only patched
|
||||
# the current terminal). If node/npm resolve normally but not here, that
|
||||
# is almost always the cause - fail with a clear message instead of a
|
||||
# cryptic "npm is not recognized" a few lines down.
|
||||
$npmCommand = Get-Command npm.cmd -ErrorAction SilentlyContinue
|
||||
if (-not $npmCommand) { $npmCommand = Get-Command npm -ErrorAction SilentlyContinue }
|
||||
$nodeCommand = Get-Command node.exe -ErrorAction SilentlyContinue
|
||||
if (-not $nodeCommand) { $nodeCommand = Get-Command node -ErrorAction SilentlyContinue }
|
||||
if (-not $npmCommand -or -not $nodeCommand) {
|
||||
Write-Host ""
|
||||
Write-Host "FEHLER: npm/node wurden in diesem administrativen Fenster nicht gefunden." -ForegroundColor Red
|
||||
Write-Host "npm gefunden: $([bool]$npmCommand) node gefunden: $([bool]$nodeCommand)" -ForegroundColor Red
|
||||
Write-Host "Ein 'Als Administrator ausfuehren'-Prozess bekommt seine Umgebung frisch aus der Registry -" -ForegroundColor Red
|
||||
Write-Host "PATH-Aenderungen, die nur in deiner normalen (nicht-elevierten) Sitzung gelten (z.B. nvm/Volta" -ForegroundColor Red
|
||||
Write-Host "ohne dauerhafte Registry-Eintragung), sind hier nicht sichtbar, obwohl 'npm run dev' normal funktioniert." -ForegroundColor Red
|
||||
Write-Host "PATH in diesem Fenster:" -ForegroundColor DarkGray
|
||||
Write-Host $env:PATH -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host "Loesung: node/npm dauerhaft im PATH eintragen (System-Umgebungsvariablen, nicht nur die Sitzung)," -ForegroundColor Yellow
|
||||
Write-Host "z.B. ueber die Windows-Systemsteuerung 'Umgebungsvariablen bearbeiten' oder 'setx PATH ...'." -ForegroundColor Yellow
|
||||
throw "npm/node nicht im administrativen PATH gefunden."
|
||||
}
|
||||
|
||||
# Kill stale instances of THIS project BEFORE checking the port: a leftover
|
||||
# electron/vite/node/input-helper cluster from a previous run (e.g. a window
|
||||
# force-closed via Task Manager, which skips main.ts's graceful will-quit
|
||||
# cleanup and orphans the spawned input-helper.ps1 child) is the most common
|
||||
# reason a fresh start silently talks to an old instance instead of
|
||||
# replacing it. Shared with "npm run dev" itself via the predev hook, so
|
||||
# both start paths get the same guarantee.
|
||||
& (Join-Path $PSScriptRoot "kill-stale-instances.ps1")
|
||||
|
||||
$devPort = 5173
|
||||
$portInUse = $null
|
||||
try {
|
||||
$portInUse = Get-NetTCPConnection -LocalPort $devPort -State Listen -ErrorAction Stop | Select-Object -First 1
|
||||
} catch {
|
||||
# Get-NetTCPConnection kann auf manchen Systemen fehlen; das ist kein Fehler.
|
||||
$portInUse = $null
|
||||
}
|
||||
if ($portInUse) {
|
||||
$portOwner = Get-Process -Id $portInUse.OwningProcess -ErrorAction SilentlyContinue
|
||||
Write-Host ""
|
||||
Write-Host "WARNUNG: Port $devPort ist noch belegt (Prozess: $($portOwner.ProcessName), PID $($portInUse.OwningProcess)) - auch nach dem Beenden alter Instanzen dieses Projekts." -ForegroundColor Yellow
|
||||
Write-Host "Das ist wahrscheinlich ein unabhaengiger Prozess (z.B. ein anderes Projekt auf Port $devPort)." -ForegroundColor Yellow
|
||||
Write-Host "'npm run dev' weicht dann auf einen anderen Port aus oder verbindet sich mit dem falschen Server." -ForegroundColor Yellow
|
||||
Write-Host "Falls noetig: Prozess PID $($portInUse.OwningProcess) manuell beenden und dieses Fenster neu starten." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Set-Location -LiteralPath $project
|
||||
Write-Host "Starte npm run dev (Administrator)..."
|
||||
npm run dev
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
if ($exitCode -ne 0) {
|
||||
Write-Host ""
|
||||
Write-Host "npm run dev wurde mit Fehlercode $exitCode beendet." -ForegroundColor Red
|
||||
Write-Host "Die Ausgabe oben zeigt die genaue Ursache (z.B. belegter Port, fehlende Abhaengigkeiten)." -ForegroundColor Red
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "npm run dev wurde beendet (Fenster schliessen oder Strg+C fuer Neustart)."
|
||||
}
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Host "Fehler beim Start: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host $_.ScriptStackTrace -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Dieses Fenster bleibt offen (siehe Meldungen oben). Schliessen mit Enter oder dem Fenster-X."
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Beendet alle laufenden Prozesse dieses Projekts (Electron, Node/Vite, und
|
||||
# einen verwaisten lokalen Input-Helper-Kindprozess), bevor eine neue Instanz
|
||||
# startet.
|
||||
#
|
||||
# Warum das noetig ist: Ein normales Schliessen der App (X-Button) raeumt
|
||||
# sauber auf (main.ts: app.on("will-quit") beendet den gespawnten
|
||||
# input-helper.ps1-Kindprozess). Wird die App aber gewaltsam beendet - z.B.
|
||||
# ueber Task-Manager "Task beenden" oder ein Skript, das Stop-Process -Force
|
||||
# auf den Electron-Prozess anwendet - greift dieser Cleanup-Handler NICHT,
|
||||
# und der Kindprozess (PowerShell mit dem Input-Helper) bleibt als Waise
|
||||
# aktiv, obwohl das Hauptfenster laengst weg ist. Dieses Skript faengt genau
|
||||
# das ab: es wird vor JEDEM Start (npm run dev und npm run dev:admin, ueber
|
||||
# den "predev"-Hook) ausgefuehrt und raeumt vorherige Instanzen kompromisslos
|
||||
# weg, egal wie sie beendet wurden.
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$project = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
|
||||
$electronPath = Join-Path $project "node_modules\electron\dist\electron.exe"
|
||||
|
||||
$killed = 0
|
||||
|
||||
Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
($_.Name -eq "electron.exe" -and $_.ExecutablePath -eq $electronPath) -or
|
||||
($_.Name -eq "node.exe" -and $_.CommandLine -like "*$project*") -or
|
||||
($_.Name -eq "powershell.exe" -and $_.CommandLine -like "*input-helper.ps1*")
|
||||
} |
|
||||
ForEach-Object {
|
||||
Write-Host "Beende alte Instanz: $($_.Name) (PID $($_.ProcessId))"
|
||||
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
$killed++
|
||||
}
|
||||
|
||||
if ($killed -gt 0) {
|
||||
# Windows braucht einen Moment, um Ports/Handles wirklich freizugeben.
|
||||
Start-Sleep -Milliseconds 500
|
||||
Write-Host "$killed alte Prozess(e) beendet."
|
||||
} else {
|
||||
Write-Host "Keine alten Instanzen gefunden."
|
||||
}
|
||||
Reference in New Issue
Block a user