From 60f72f4ecf45711c6066349ffbd39d1b01f7ac30 Mon Sep 17 00:00:00 2001 From: mfoltz Date: Wed, 6 May 2026 11:05:58 -0500 Subject: [PATCH 1/6] Add Blood Hunts localization GUID bridge --- scripts/blood-hunts.test.ts | 29 +++++++++++++++++++++++++++-- scripts/blood-hunts.ts | 32 +++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/scripts/blood-hunts.test.ts b/scripts/blood-hunts.test.ts index d5a15e92e5..29d9a8683f 100644 --- a/scripts/blood-hunts.test.ts +++ b/scripts/blood-hunts.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; -import { buildBloodHuntsMapSnapshot } from "./blood-hunts"; +import { buildBloodHuntsMapSnapshot, nameKeyToLocalizationGuid } from "./blood-hunts"; type TestCase = { name: string; @@ -34,7 +34,7 @@ function validSource(overrides: Record = {}) { Level: 53, HideLevel: 1, PrefabGUID: { _Value: 795262842 }, - Name: { Key: { _a: 1, _b: 2, _c: 3, _d: 4 } }, + Name: { Key: { _a: 1819955650, _b: -1253071585, _c: 362143916, _d: 236122505 } }, AssetGuid: "must-not-be-promoted", SpritePathID: 987 } @@ -49,14 +49,23 @@ async function buildFixture(sourceFile: string, overrides: Partial { + assert.equal( + nameKeyToLocalizationGuid({ _a: 1819955650, _b: -1253071585, _c: 362143916, _d: 236122505 }), + "c2517a6c-1fa5-4fb5-ace0-951589f1120e" + ); +}); + test("buildBloodHuntsMapSnapshot keys rows only by PrefabGUID._Value", async () => { await withSourceFile(validSource(), async (sourceFile) => { const snapshot = await buildFixture(sourceFile); @@ -75,6 +84,14 @@ test("buildBloodHuntsMapSnapshot converts HideLevel to boolean and records sourc }); }); +test("buildBloodHuntsMapSnapshot stores source-backed nameLocalizationGuid", async () => { + await withSourceFile(validSource(), async (sourceFile) => { + const snapshot = await buildFixture(sourceFile); + const entry = snapshot.entriesByGuid["795262842"]; + assert.equal(entry.nameLocalizationGuid, "c2517a6c-1fa5-4fb5-ace0-951589f1120e"); + }); +}); + test("buildBloodHuntsMapSnapshot requires source-backed prefab, localization, and NPC display joins", async () => { await withSourceFile(validSource(), async (sourceFile) => { await assert.rejects( @@ -85,6 +102,14 @@ test("buildBloodHuntsMapSnapshot requires source-backed prefab, localization, an () => buildFixture(sourceFile, { localizedNamesByGuid: {} }), /missing localized name join/ ); + await assert.rejects( + () => buildFixture(sourceFile, { localizedTextByGuid: {} }), + /missing localized text join/ + ); + await assert.rejects( + () => buildFixture(sourceFile, { localizedTextByGuid: { "c2517a6c-1fa5-4fb5-ace0-951589f1120e": "Wrong Name" } }), + /localized text mismatch/ + ); await assert.rejects( () => buildFixture(sourceFile, { npcDisplayByPrefab: {} }), /missing NPC display join/ diff --git a/scripts/blood-hunts.ts b/scripts/blood-hunts.ts index 946f463660..7685fc4d0f 100644 --- a/scripts/blood-hunts.ts +++ b/scripts/blood-hunts.ts @@ -14,6 +14,7 @@ export interface BloodHuntsJoinProvenance { sourceRef: string; prefabSourceRef: string; localizedNameSourceRef: string; + localizedTextSourceRef: string; npcDisplaySourceRef: string; hideLevelSourceValue: number; } @@ -24,6 +25,7 @@ export interface BloodHuntsMapEntry { bloodHuntLevel: number; bloodHuntHideLevel: boolean; nameKey: BloodHuntsNameKey; + nameLocalizationGuid: string; provenance: BloodHuntsJoinProvenance; } @@ -40,9 +42,11 @@ export interface BloodHuntsBuildOptions { sourceRef: string; prefabByGuid: Map; localizedNamesByGuid: Record; + localizedTextByGuid: Record | Map; npcDisplayByPrefab: Record; prefabSourceRef: string; localizedNameSourceRef: string; + localizedTextSourceRef: string; npcDisplaySourceRef: string; } @@ -72,6 +76,20 @@ function readNameKey(value: unknown, source: string): BloodHuntsNameKey { }; } +export function nameKeyToLocalizationGuid(key: BloodHuntsNameKey): string { + const bytes = Buffer.alloc(16); + bytes.writeInt32LE(key._a, 0); + bytes.writeInt32LE(key._b, 4); + bytes.writeInt32LE(key._c, 8); + bytes.writeInt32LE(key._d, 12); + const hex = bytes.toString("hex"); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} + +function readLocalizedText(localizedTextByGuid: BloodHuntsBuildOptions["localizedTextByGuid"], guid: string): string | undefined { + return localizedTextByGuid instanceof Map ? localizedTextByGuid.get(guid.toLowerCase()) : localizedTextByGuid[guid.toLowerCase()]; +} + function parseBloodHuntsRows(raw: unknown, sourceRef: string): BloodHuntsMapEntry[] { if (!isRecord(raw) || !Array.isArray(raw.VBloodDatas)) { throw new Error(`${sourceRef}: expected VBloodDatas array`); @@ -87,17 +105,20 @@ function parseBloodHuntsRows(raw: unknown, sourceRef: string): BloodHuntsMapEntr throw new Error(`${source}: missing PrefabGUID._Value`); } const hideLevelSourceValue = readNumber(row, "HideLevel", source); + const nameKey = readNameKey(isRecord(row.Name) ? row.Name.Key : undefined, source); return { prefab: "", guid: prefabGuid, bloodHuntLevel: readNumber(row, "Level", source), bloodHuntHideLevel: hideLevelSourceValue !== 0, - nameKey: readNameKey(isRecord(row.Name) ? row.Name.Key : undefined, source), + nameKey, + nameLocalizationGuid: nameKeyToLocalizationGuid(nameKey), provenance: { sourceKind: bloodHuntsSourceKind, sourceRef, prefabSourceRef: "", localizedNameSourceRef: "", + localizedTextSourceRef: "", npcDisplaySourceRef: "", hideLevelSourceValue } @@ -126,6 +147,14 @@ export async function buildBloodHuntsMapSnapshot(options: BloodHuntsBuildOptions throw new Error(`${options.sourceRef}:${guidKey}: missing localized name join in ${options.localizedNameSourceRef}`); } + const localizedText = readLocalizedText(options.localizedTextByGuid, row.nameLocalizationGuid)?.trim(); + if (!localizedText) { + throw new Error(`${options.sourceRef}:${guidKey}: missing localized text join for ${row.nameLocalizationGuid} in ${options.localizedTextSourceRef}`); + } + if (localizedText !== localizedName) { + throw new Error(`${options.sourceRef}:${guidKey}: localized text mismatch for ${row.nameLocalizationGuid} between ${options.localizedTextSourceRef} and ${options.localizedNameSourceRef}`); + } + const displayName = options.npcDisplayByPrefab[prefab]?.displayNameEn?.trim(); if (!displayName) { throw new Error(`${options.sourceRef}:${guidKey}: missing NPC display join in ${options.npcDisplaySourceRef}`); @@ -138,6 +167,7 @@ export async function buildBloodHuntsMapSnapshot(options: BloodHuntsBuildOptions ...row.provenance, prefabSourceRef: options.prefabSourceRef, localizedNameSourceRef: options.localizedNameSourceRef, + localizedTextSourceRef: options.localizedTextSourceRef, npcDisplaySourceRef: options.npcDisplaySourceRef } }); From 35823169b04d2c95b56ac75d9e5a407534e353b2 Mon Sep 17 00:00:00 2001 From: mfoltz Date: Wed, 6 May 2026 11:06:13 -0500 Subject: [PATCH 2/6] Add NPC portrait bridge generation --- package.json | 2 +- scripts/npc-portraits.test.ts | 205 +++++++++++++++ scripts/npc-portraits.ts | 402 +++++++++++++++++++++++++++++ scripts/refresh-db-assets.ts | 20 ++ scripts/validate-generated-data.ts | 119 ++++++++- 5 files changed, 746 insertions(+), 2 deletions(-) create mode 100644 scripts/npc-portraits.test.ts create mode 100644 scripts/npc-portraits.ts diff --git a/package.json b/package.json index ded5dfe335..c4b923c4cc 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build": "npm run generate:data && npm run validate:data && vite build && jiti scripts/prepare-pages-fallback.ts", "preview": "vite preview", "verify": "npx tsc --noEmit && node scripts/check_shortcode_syntax.js content && npm run build && npm run verify:artifact", - "test": "npm run test:visual-review && npm run test:ability-damage-evidence && tsx --test src/components/layout/SiteShell.test.tsx", + "test": "npm run test:visual-review && npm run test:ability-damage-evidence && tsx scripts/blood-hunts.test.ts && tsx scripts/npc-portraits.test.ts && tsx --test src/components/layout/SiteShell.test.tsx", "visual:baseline": "npm run build && npm run visual:baseline:capture", "visual:baseline:capture": "jiti scripts/visual-review.ts baseline", "visual:compare": "npm run build && npm run visual:compare:capture", diff --git a/scripts/npc-portraits.test.ts b/scripts/npc-portraits.test.ts new file mode 100644 index 0000000000..a948695fcc --- /dev/null +++ b/scripts/npc-portraits.test.ts @@ -0,0 +1,205 @@ +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { buildNpcPortraitSnapshots } from "./npc-portraits"; + +type TestCase = { + name: string; + run: () => void | Promise; +}; + +const tests: TestCase[] = []; + +function test(name: string, run: TestCase["run"]) { + tests.push({ name, run }); +} + +async function withAssetDump(fileNames: string[], run: (assetDumpDir: string) => Promise): Promise { + const tmp = await mkdtemp(path.join(tmpdir(), "npc-portraits-")); + try { + await mkdir(path.join(tmp, "Sprite")); + await mkdir(path.join(tmp, "Texture2D")); + for (const fileName of fileNames) { + await writeFile(path.join(tmp, "Sprite", fileName), ""); + await writeFile(path.join(tmp, "Texture2D", fileName), ""); + } + await run(tmp); + } finally { + await rm(tmp, { recursive: true, force: true }); + } +} + +const allPrefabs = { + CHAR_Bandit_Bomber_VBlood: 1896428751, + CHAR_Villager_Tailor_VBlood: -1942352521, + CHAR_Vampire_BloodKnight_VBlood: -100, + CHAR_Vampire_IceRanger_VBlood: -200, + CHAR_Bandit_Frostarrow_VBlood: 1124739990, + CHAR_Forest_Bear_Dire_Vblood: -1391546313, + CHAR_Gloomrot_Purifier_VBlood: 1891432250, + CHAR_Cursed_ToadKing_VBlood: 712813874, + CHAR_Villager_CursedWanderer_VBlood: 109969450 +}; + +const npcDisplayByPrefab = { + CHAR_Bandit_Bomber_VBlood: { displayNameEn: "Clive the Firestarter" }, + CHAR_Villager_Tailor_VBlood: { displayNameEn: "Beatrice the Tailor" }, + CHAR_Vampire_BloodKnight_VBlood: { displayNameEn: "General Valencia the Depraved" }, + CHAR_Vampire_IceRanger_VBlood: { displayNameEn: "General Elena the Hollow" }, + CHAR_Bandit_Frostarrow_VBlood: { displayNameEn: "Keely the Frost Archer" }, + CHAR_Forest_Bear_Dire_Vblood: { displayNameEn: "Kodia the Ferocious Bear" }, + CHAR_Gloomrot_Purifier_VBlood: { displayNameEn: "Angram the Purifier" }, + CHAR_Cursed_ToadKing_VBlood: { displayNameEn: "Albert the Duke of Balaton" }, + CHAR_Villager_CursedWanderer_VBlood: { displayNameEn: "Ben the Old Wanderer" } +}; + +const npcClassificationByPrefab = { + CHAR_Bandit_Bomber_VBlood: { prefab: "CHAR_Bandit_Bomber_VBlood", guid: 1896428751, isVBlood: true, bloodType: "V Blood" }, + CHAR_Villager_Tailor_VBlood: { prefab: "CHAR_Villager_Tailor_VBlood", guid: -1942352521, isVBlood: true, bloodType: "V Blood" }, + CHAR_Vampire_BloodKnight_VBlood: { prefab: "CHAR_Vampire_BloodKnight_VBlood", guid: -100, isVBlood: true, bloodType: "V Blood" }, + CHAR_Vampire_IceRanger_VBlood: { prefab: "CHAR_Vampire_IceRanger_VBlood", guid: -200, isVBlood: true, bloodType: "V Blood" }, + CHAR_Bandit_Frostarrow_VBlood: { prefab: "CHAR_Bandit_Frostarrow_VBlood", guid: 1124739990, isVBlood: true, bloodType: "V Blood" }, + CHAR_Forest_Bear_Dire_Vblood: { prefab: "CHAR_Forest_Bear_Dire_Vblood", guid: -1391546313, isVBlood: true, bloodType: "V Blood" }, + CHAR_Gloomrot_Purifier_VBlood: { prefab: "CHAR_Gloomrot_Purifier_VBlood", guid: 1891432250, isVBlood: true, bloodType: "V Blood" }, + CHAR_Cursed_ToadKing_VBlood: { prefab: "CHAR_Cursed_ToadKing_VBlood", guid: 712813874, isVBlood: true, bloodType: "V Blood" }, + CHAR_Villager_CursedWanderer_VBlood: { prefab: "CHAR_Villager_CursedWanderer_VBlood", guid: 109969450, isVBlood: true, bloodType: "V Blood" } +}; + +const bloodHuntsByGuid = { + "1896428751": { prefab: "CHAR_Bandit_Bomber_VBlood", guid: 1896428751 }, + "-1942352521": { prefab: "CHAR_Villager_Tailor_VBlood", guid: -1942352521 }, + "-100": { prefab: "CHAR_Vampire_BloodKnight_VBlood", guid: -100 }, + "-200": { prefab: "CHAR_Vampire_IceRanger_VBlood", guid: -200 }, + "1124739990": { prefab: "CHAR_Bandit_Frostarrow_VBlood", guid: 1124739990 }, + "-1391546313": { prefab: "CHAR_Forest_Bear_Dire_Vblood", guid: -1391546313 }, + "1891432250": { prefab: "CHAR_Gloomrot_Purifier_VBlood", guid: 1891432250 }, + "712813874": { prefab: "CHAR_Cursed_ToadKing_VBlood", guid: 712813874 }, + "109969450": { prefab: "CHAR_Villager_CursedWanderer_VBlood", guid: 109969450 } +}; + +const vbloodNamesRows = [ + ["Clive", "CHAR_Bandit_Bomber_VBlood", "VBlood"], + ["Beatrice the Tailor", "CHAR_Villager_Tailor_VBlood", "VBlood"], + ["General Valencia the Depraved", "CHAR_Vampire_BloodKnight_VBlood", "VBlood"], + ["General Elena the Hollow", "CHAR_Vampire_IceRanger_VBlood", "VBlood"], + ["Keely", "CHAR_Bandit_Frostarrow_VBlood", "VBlood"], + ["Kodia the Ferocious Bear", "CHAR_Forest_Bear_Dire_Vblood", "VBlood"], + ["Angram the Purifier", "CHAR_Gloomrot_Purifier_VBlood", "VBlood"], + ["Albert The Duke of Balaton", "CHAR_Cursed_ToadKing_VBlood", "VBlood"], + ["Ben The Old Wanderer", "CHAR_Villager_CursedWanderer_VBlood", "VBlood"] +] as Array<[string, string, string]>; + +async function buildFixture(assetDumpDir: string) { + return await buildNpcPortraitSnapshots({ + assetDumpDir, + allPrefabs, + npcDisplayByPrefab, + npcClassificationByPrefab, + bloodHuntsByGuid, + vbloodNamesRows + }); +} + +test("buildNpcPortraitSnapshots promotes exact and approved user-attested rows only", async () => { + await withAssetDump( + [ + "CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "Portrait_Large_Normal_BeatriceTailor.png", + "Portrait_Large_Normal_BloodCommander.png", + "Portrait_Large_Normal_FrostCommander.png", + "Portrait_Large_Normal_KeelyFrostArcher.png" + ], + async (assetDumpDir) => { + const { candidates, portraitMap } = await buildFixture(assetDumpDir); + + assert.equal(candidates.entriesByAssetName["CHAR_Bandit_Bomber_VBlood_HeadPortrait.png"].joinStatus, "source-backed"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BeatriceTailor.png"].joinStatus, "circumstantial"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BeatriceTailor.png"].candidatePrefab, "CHAR_Villager_Tailor_VBlood"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BloodCommander.png"].joinStatus, "user-attested"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BloodCommander.png"].approvalStatus, "approved"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_FrostCommander.png"].candidatePrefab, "CHAR_Vampire_IceRanger_VBlood"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_KeelyFrostArcher.png"].joinStatus, "circumstantial"); + + assert.equal(portraitMap.entriesByPrefab.CHAR_Bandit_Bomber_VBlood.portraitAssetName, "CHAR_Bandit_Bomber_VBlood_HeadPortrait.png"); + assert.equal(portraitMap.entriesByPrefab.CHAR_Vampire_BloodKnight_VBlood.joinStatus, "user-attested"); + assert.equal(portraitMap.entriesByPrefab.CHAR_Vampire_IceRanger_VBlood.approvalStatus, "approved"); + assert.equal(portraitMap.entriesByPrefab.CHAR_Villager_Tailor_VBlood, undefined); + assert.equal(portraitMap.entriesByPrefab.CHAR_Bandit_Frostarrow_VBlood, undefined); + } + ); +}); + +test("buildNpcPortraitSnapshots keeps title-fragment fuzzy matches candidate-only", async () => { + await withAssetDump( + [ + "Portrait_Large_Normal_FerociousBear.png", + "Portrait_Large_Normal_Purifier.png", + "Portrait_Small_Normal_DukeBalaton.png", + "Portrait_Small_Smoke_CursedWanderer.png" + ], + async (assetDumpDir) => { + const { candidates, portraitMap } = await buildFixture(assetDumpDir); + const expected = [ + ["Portrait_Large_Normal_FerociousBear.png", "CHAR_Forest_Bear_Dire_Vblood", "Kodia the Ferocious Bear"], + ["Portrait_Large_Normal_Purifier.png", "CHAR_Gloomrot_Purifier_VBlood", "Angram the Purifier"], + ["Portrait_Small_Normal_DukeBalaton.png", "CHAR_Cursed_ToadKing_VBlood", "Albert the Duke of Balaton"], + ["Portrait_Small_Smoke_CursedWanderer.png", "CHAR_Villager_CursedWanderer_VBlood", "Ben the Old Wanderer"] + ] as const; + + for (const [assetName, prefab, displayNameEn] of expected) { + const candidate = candidates.entriesByAssetName[assetName]; + assert.equal(candidate.joinStatus, "circumstantial"); + assert.equal(candidate.approvalStatus, "pending"); + assert.equal(candidate.candidatePrefab, prefab); + assert.equal(candidate.displayNameEn, displayNameEn); + assert.equal(portraitMap.entriesByPrefab[prefab], undefined); + } + } + ); +}); + +test("buildNpcPortraitSnapshots marks fuzzy matches unsafe when ambiguous", async () => { + await withAssetDump(["Portrait_Large_Normal_Purifier.png"], async (assetDumpDir) => { + const { candidates } = await buildNpcPortraitSnapshots({ + assetDumpDir, + allPrefabs: { + CHAR_Gloomrot_Purifier_VBlood: 1891432250, + CHAR_Test_Purifier_VBlood: 42 + }, + npcDisplayByPrefab: { + CHAR_Gloomrot_Purifier_VBlood: { displayNameEn: "Angram the Purifier" }, + CHAR_Test_Purifier_VBlood: { displayNameEn: "Example the Purifier" } + }, + npcClassificationByPrefab: { + CHAR_Gloomrot_Purifier_VBlood: { guid: 1891432250, isVBlood: true, bloodType: "V Blood" }, + CHAR_Test_Purifier_VBlood: { guid: 42, isVBlood: true, bloodType: "V Blood" } + }, + bloodHuntsByGuid: { + "1891432250": { prefab: "CHAR_Gloomrot_Purifier_VBlood", guid: 1891432250 }, + "42": { prefab: "CHAR_Test_Purifier_VBlood", guid: 42 } + }, + vbloodNamesRows: [ + ["Angram the Purifier", "CHAR_Gloomrot_Purifier_VBlood", "VBlood"], + ["Example the Purifier", "CHAR_Test_Purifier_VBlood", "VBlood"] + ] + }); + + const candidate = candidates.entriesByAssetName["Portrait_Large_Normal_Purifier.png"]; + assert.equal(candidate.joinStatus, "unsafe"); + assert.equal(candidate.approvalStatus, "rejected"); + assert.match(candidate.reason, /multiple current NPC rows/); + }); +}); + +async function main() { + for (const { name, run } of tests) { + await run(); + console.log(`ok - ${name}`); + } +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/npc-portraits.ts b/scripts/npc-portraits.ts new file mode 100644 index 0000000000..8753f77451 --- /dev/null +++ b/scripts/npc-portraits.ts @@ -0,0 +1,402 @@ +import { readdir } from "node:fs/promises"; +import path from "node:path"; + +export const npcPortraitCandidatesSourceKind = "provisional-vblood-portrait-candidates" as const; +export const npcPortraitMapSourceKind = "provisional-vblood-portrait-map" as const; + +export type NpcPortraitJoinStatus = "source-backed" | "user-attested" | "circumstantial" | "unsafe"; +export type NpcPortraitApprovalStatus = "pending" | "approved" | "rejected"; + +export interface NpcPortraitCandidateEntry { + assetName: string; + assetFamily: string; + assetSourceRefs: string[]; + joinStatus: NpcPortraitJoinStatus; + approvalStatus?: NpcPortraitApprovalStatus; + approvalNote?: string; + candidatePrefab?: string; + candidateGuid?: number; + displayNameEn?: string; + evidenceRefs: string[]; + reason: string; +} + +export interface NpcPortraitCandidatesSnapshot { + schemaVersion: 1; + sourceKind: typeof npcPortraitCandidatesSourceKind; + sourceRefs: string[]; + totalAssets: number; + currentVbloodRows: number; + entriesByAssetName: Record; +} + +export interface NpcPortraitMapEntry { + prefab: string; + guid: number; + displayNameEn: string; + portraitAssetName: string; + portraitAssetFamily: string; + joinStatus: Extract; + approvalStatus?: Extract; + approvalNote?: string; + evidenceRefs: string[]; +} + +export interface NpcPortraitMapSnapshot { + schemaVersion: 1; + sourceKind: typeof npcPortraitMapSourceKind; + sourceRef: "data/enrichment/npc-portrait-candidates.json"; + totalCurrentVbloodRows: number; + entriesByPrefab: Record; +} + +export interface NpcPortraitBuildOptions { + assetDumpDir: string; + allPrefabs: Record; + npcDisplayByPrefab: Record; + npcClassificationByPrefab: Record; + bloodHuntsByGuid: Record; + vbloodNamesRows: Array<[string, string, string]>; +} + +type AssetRecord = { + assetName: string; + assetFamily: string; + sourceRefs: string[]; +}; + +type CurrentNpcRow = { + prefab: string; + guid: number; + displayNameEn: string; + aliases: Set; + fuzzyAliases: Set; +}; + +const manualAttestedPortraits: Record = { + bloodcommander: { + prefab: "CHAR_Vampire_BloodKnight_VBlood", + approvalNote: "User-attested approved match: BloodCommander is General Valencia the Depraved." + }, + frostcommander: { + prefab: "CHAR_Vampire_IceRanger_VBlood", + approvalNote: "User-attested approved match: FrostCommander is General Elena the Hollow." + } +}; + +const unsafePrefabPattern = /(?:GateBoss|Primal|Minion|(?:^|_)Tail(?:_|$)|ShadowClone|_UNUSED)/i; +const stopWords = new Set(["the", "of"]); + +function normalizeAlias(value: string): string { + return value.toLowerCase().replace(/[^a-z0-9]+/g, ""); +} + +function normalizeWithoutStopWords(value: string): string { + return value + .split(/[^A-Za-z0-9]+/g) + .filter((part) => part && !stopWords.has(part.toLowerCase())) + .join("") + .toLowerCase(); +} + +function aliasTokens(value: string): string[] { + return value + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") + .split(/[^A-Za-z0-9]+/g) + .map((part) => part.toLowerCase()) + .filter(Boolean); +} + +function addAlias(aliases: Set, value: string | undefined): void { + if (!value) { + return; + } + const normalized = normalizeAlias(value); + if (normalized) { + aliases.add(normalized); + } + const withoutStopWords = normalizeWithoutStopWords(value); + if (withoutStopWords) { + aliases.add(withoutStopWords); + } +} + +function addFuzzyAliases(aliases: Set, value: string | undefined): void { + if (!value) { + return; + } + const tokens = aliasTokens(value).filter((part) => !stopWords.has(part)); + for (let index = 1; index < tokens.length; index++) { + const suffix = tokens.slice(index).join(""); + if (suffix.length >= 8) { + aliases.add(suffix); + } + } +} + +function prefabStem(prefab: string): string { + return prefab.replace(/^CHAR_/i, "").replace(/_VBlood(?:_.*)?$/i, "").replace(/_Vblood(?:_.*)?$/i, ""); +} + +function sortSourceRefs(sourceRefs: string[]): string[] { + const rank = (sourceRef: string) => (sourceRef.startsWith("Texture2D/") ? 0 : sourceRef.startsWith("Sprite/") ? 1 : 2); + return [...sourceRefs].sort((left, right) => rank(left) - rank(right) || left.localeCompare(right)); +} + +function assetFamily(assetName: string): string | undefined { + if (/^CHAR_.*_VBlood_HeadPortrait\.png$/i.test(assetName)) { + return "char-vblood-headportrait"; + } + const match = assetName.match(/^Portrait_(Large|Small)_(Normal|Smoke)_.+\.png$/i); + if (match) { + return `portrait-${match[1].toLowerCase()}-${match[2].toLowerCase()}`; + } + return undefined; +} + +function portraitSubject(assetName: string): string { + return assetName + .replace(/\.png$/i, "") + .replace(/^Portrait_(?:Large|Small)_(?:Normal|Smoke)_/i, "") + .replace(/^CHAR_/i, "CHAR_") + .replace(/_HeadPortrait$/i, ""); +} + +async function readPortraitAssets(assetDumpDir: string): Promise { + const assets = new Map(); + for (const directoryName of ["Sprite", "Texture2D"]) { + const directoryPath = path.join(assetDumpDir, directoryName); + let fileNames: string[] = []; + try { + fileNames = await readdir(directoryPath); + } catch (error) { + if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") { + continue; + } + throw error; + } + + for (const fileName of fileNames) { + const family = assetFamily(fileName); + if (!family) { + continue; + } + const existing = assets.get(fileName); + const sourceRef = `${directoryName}/${fileName}`; + if (existing) { + existing.sourceRefs.push(sourceRef); + } else { + assets.set(fileName, { assetName: fileName, assetFamily: family, sourceRefs: [sourceRef] }); + } + } + } + return [...assets.values()].map((asset) => ({ ...asset, sourceRefs: sortSourceRefs(asset.sourceRefs) })).sort((left, right) => left.assetName.localeCompare(right.assetName)); +} + +function buildCurrentRows(options: NpcPortraitBuildOptions): Map { + const rows = new Map(); + const bloodHuntsPrefabs = new Set(Object.values(options.bloodHuntsByGuid).map((entry) => entry?.prefab).filter((value): value is string => Boolean(value))); + const aliasesByPrefab = new Map(); + for (const [name, prefab, type] of options.vbloodNamesRows) { + if (type !== "VBlood") { + continue; + } + const aliases = aliasesByPrefab.get(prefab) ?? []; + aliases.push(name); + aliasesByPrefab.set(prefab, aliases); + } + + for (const [prefab, classification] of Object.entries(options.npcClassificationByPrefab)) { + const guid = options.allPrefabs[prefab] ?? classification?.guid; + const displayNameEn = options.npcDisplayByPrefab[prefab]?.displayNameEn?.trim(); + const isCurrentVblood = classification?.isVBlood === true || classification?.bloodType === "V Blood" || bloodHuntsPrefabs.has(prefab); + if (!isCurrentVblood || typeof guid !== "number" || !displayNameEn) { + continue; + } + + const aliases = new Set(); + const fuzzyAliases = new Set(); + addAlias(aliases, prefab); + addAlias(aliases, prefabStem(prefab)); + addAlias(aliases, displayNameEn); + addFuzzyAliases(fuzzyAliases, prefabStem(prefab)); + addFuzzyAliases(fuzzyAliases, displayNameEn); + for (const alias of aliasesByPrefab.get(prefab) ?? []) { + addAlias(aliases, alias); + addFuzzyAliases(fuzzyAliases, alias); + } + + rows.set(prefab, { prefab, guid, displayNameEn, aliases, fuzzyAliases }); + } + return rows; +} + +function unsafeReasonForPrefab(prefab: string | undefined): string | undefined { + if (!prefab) { + return undefined; + } + return unsafePrefabPattern.test(prefab) ? "candidate points at a Primal, GateBoss, minion, tail, shadow clone, or unused prefab" : undefined; +} + +function classifyAsset(asset: AssetRecord, currentRows: Map): NpcPortraitCandidateEntry { + const subject = portraitSubject(asset.assetName); + const evidenceRefs = [...asset.sourceRefs, "data/enrichment/npc-display-map.json", "data/enrichment/npc-classification-map.json"]; + + if (asset.assetFamily === "char-vblood-headportrait") { + const prefab = subject.replace(/_VBlood$/i, "_VBlood"); + const row = currentRows.get(prefab); + const unsafeReason = unsafeReasonForPrefab(prefab); + if (row && !unsafeReason) { + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "source-backed", + candidatePrefab: row.prefab, + candidateGuid: row.guid, + displayNameEn: row.displayNameEn, + evidenceRefs: [...evidenceRefs, "data/prefabs/All.json"], + reason: "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }; + } + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "unsafe", + approvalStatus: "rejected", + ...(row ? { candidatePrefab: row.prefab, candidateGuid: row.guid, displayNameEn: row.displayNameEn } : {}), + evidenceRefs, + reason: unsafeReason ?? "asset basename does not join to a current V Blood NPC row" + }; + } + + const normalizedSubject = normalizeAlias(subject); + const manual = manualAttestedPortraits[normalizedSubject]; + if (manual) { + const row = currentRows.get(manual.prefab); + const unsafeReason = unsafeReasonForPrefab(manual.prefab); + if (row && !unsafeReason) { + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "user-attested", + approvalStatus: "approved", + approvalNote: manual.approvalNote, + candidatePrefab: row.prefab, + candidateGuid: row.guid, + displayNameEn: row.displayNameEn, + evidenceRefs: [...evidenceRefs, "data/prefabs/VBloodNames.json"], + reason: "approved user-attested portrait alias" + }; + } + } + + const rowsEligibleForMatching = [...currentRows.values()].filter((row) => !unsafeReasonForPrefab(row.prefab)); + const matches = rowsEligibleForMatching.filter((row) => row.aliases.has(normalizedSubject)); + if (matches.length === 1) { + const row = matches[0]; + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "circumstantial", + approvalStatus: "pending", + candidatePrefab: row.prefab, + candidateGuid: row.guid, + displayNameEn: row.displayNameEn, + evidenceRefs: [...evidenceRefs, "data/prefabs/VBloodNames.json"], + reason: "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }; + } + + const fuzzyMatches = matches.length === 0 ? rowsEligibleForMatching.filter((row) => row.fuzzyAliases.has(normalizedSubject)) : []; + if (fuzzyMatches.length === 1) { + const row = fuzzyMatches[0]; + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "circumstantial", + approvalStatus: "pending", + candidatePrefab: row.prefab, + candidateGuid: row.guid, + displayNameEn: row.displayNameEn, + evidenceRefs: [...evidenceRefs, "data/prefabs/VBloodNames.json"], + reason: "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }; + } + + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "unsafe", + approvalStatus: "rejected", + evidenceRefs, + reason: + matches.length > 1 || fuzzyMatches.length > 1 + ? "portrait alias matched multiple current NPC rows" + : "portrait alias did not match a current V Blood NPC row" + }; +} + +function toPortraitMap(candidates: NpcPortraitCandidateEntry[], totalCurrentVbloodRows: number): NpcPortraitMapSnapshot { + const usable = candidates.filter( + (entry) => entry.joinStatus === "source-backed" || (entry.joinStatus === "user-attested" && entry.approvalStatus === "approved") + ); + const rank = (entry: NpcPortraitCandidateEntry) => (entry.joinStatus === "source-backed" ? 2 : 1); + const entriesByPrefab = new Map(); + + for (const candidate of usable.sort((left, right) => rank(right) - rank(left) || left.assetName.localeCompare(right.assetName))) { + if (!candidate.candidatePrefab || typeof candidate.candidateGuid !== "number" || !candidate.displayNameEn) { + continue; + } + if (entriesByPrefab.has(candidate.candidatePrefab)) { + continue; + } + entriesByPrefab.set(candidate.candidatePrefab, { + prefab: candidate.candidatePrefab, + guid: candidate.candidateGuid, + displayNameEn: candidate.displayNameEn, + portraitAssetName: candidate.assetName, + portraitAssetFamily: candidate.assetFamily, + joinStatus: candidate.joinStatus === "source-backed" ? "source-backed" : "user-attested", + ...(candidate.joinStatus === "user-attested" ? { approvalStatus: "approved" as const } : {}), + ...(candidate.approvalNote ? { approvalNote: candidate.approvalNote } : {}), + evidenceRefs: candidate.evidenceRefs + }); + } + + return { + schemaVersion: 1, + sourceKind: npcPortraitMapSourceKind, + sourceRef: "data/enrichment/npc-portrait-candidates.json", + totalCurrentVbloodRows, + entriesByPrefab: Object.fromEntries([...entriesByPrefab.entries()].sort(([left], [right]) => left.localeCompare(right))) + }; +} + +export async function buildNpcPortraitSnapshots(options: NpcPortraitBuildOptions): Promise<{ + candidates: NpcPortraitCandidatesSnapshot; + portraitMap: NpcPortraitMapSnapshot; +}> { + const assets = await readPortraitAssets(options.assetDumpDir); + const currentRows = buildCurrentRows(options); + const candidateEntries = assets.map((asset) => classifyAsset(asset, currentRows)); + const candidates: NpcPortraitCandidatesSnapshot = { + schemaVersion: 1, + sourceKind: npcPortraitCandidatesSourceKind, + sourceRefs: ["Texture2D", "Sprite", "data/enrichment/npc-display-map.json", "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json"], + totalAssets: assets.length, + currentVbloodRows: currentRows.size, + entriesByAssetName: Object.fromEntries(candidateEntries.map((entry) => [entry.assetName, entry] as const).sort((left, right) => left[0].localeCompare(right[0]))) + }; + + return { + candidates, + portraitMap: toPortraitMap(candidateEntries, currentRows.size) + }; +} diff --git a/scripts/refresh-db-assets.ts b/scripts/refresh-db-assets.ts index 103a1b2fc3..7133a8da5a 100644 --- a/scripts/refresh-db-assets.ts +++ b/scripts/refresh-db-assets.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url"; import { assertAssetDumpLock, syncIconDirectory } from "./asset-dump-lock"; import { resolveAssetDumpDir } from "./asset-dump-resolver"; import { buildBloodHuntsMapSnapshot, bloodHuntsSourceKind, type BloodHuntsMapSnapshot } from "./blood-hunts"; +import { buildNpcPortraitSnapshots } from "./npc-portraits"; import { isNpcDisplayCandidateDoc } from "./npc-display-classification"; import { extractTextVariables, @@ -2775,6 +2776,7 @@ async function main() { const contentPrefabsDir = path.join(repoRoot, "content", "prefabs"); const enrichmentDir = path.join(repoRoot, "data", "enrichment"); const allPrefabsPath = path.join(repoRoot, "data", "prefabs", "All.json"); + const vBloodNamesPath = path.join(repoRoot, "data", "prefabs", "VBloodNames.json"); const publicAbilityIconsDir = path.join(repoRoot, "public", "icons", "abilities"); const publicItemIconsDir = path.join(repoRoot, "public", "icons", "items"); const bloodHuntsSourcePath = path.join(assetDumpDir, "MonoBehaviour", "BloodHuntsDataAuthoring.json"); @@ -2792,6 +2794,7 @@ async function main() { const sourceTextVariableValues = await loadTextVariableValues(resourcesDirs); const docs = await loadPrefabDocuments(contentPrefabsDir); const allPrefabs = parseJsonText>(await readFile(allPrefabsPath, "utf8")); + const vBloodNamesRows = parseJsonText>(await readFile(vBloodNamesPath, "utf8")); const allPrefabByGuid = new Map(Object.entries(allPrefabs).map(([prefab, guid]) => [guid, prefab])); const texturePngFiles = (await readdir(iconSourceDir)).filter((fileName) => /\.png$/i.test(fileName)); const availableIconFiles = new Set(texturePngFiles); @@ -3444,11 +3447,21 @@ async function main() { sourceRef: path.relative(assetDumpDir, bloodHuntsSourcePath).replace(/\\/g, "/"), prefabByGuid: allPrefabByGuid, localizedNamesByGuid: stableLocalizedSnapshot.namesByGuid, + localizedTextByGuid: localizedNames.englishTextByGuid, npcDisplayByPrefab: displaySnapshotsByDomain.get("npc") ?? {}, prefabSourceRef: "data/prefabs/All.json", localizedNameSourceRef: "data/enrichment/prefab-localization.json:namesByGuid", + localizedTextSourceRef: "Resources/Localization/English.json:Nodes", npcDisplaySourceRef: "data/enrichment/npc-display-map.json" }); + const stableNpcPortraitSnapshots = await buildNpcPortraitSnapshots({ + assetDumpDir, + allPrefabs, + npcDisplayByPrefab: displaySnapshotsByDomain.get("npc") ?? {}, + npcClassificationByPrefab: stableNpcClassificationSnapshot, + bloodHuntsByGuid: stableBloodHuntsSnapshot.entriesByGuid, + vbloodNamesRows: vBloodNamesRows + }); const abilityTooltipEntries = stableCatalogSnapshot.entries .map((entry) => stableTooltipSnapshot[entry.prefab]) @@ -3488,6 +3501,11 @@ async function main() { "item-icon-map": toCoverage(Object.keys(stableItemIconSnapshot).length, itemIconMatched, itemIconLowSignal), "item-description-map": toCoverage(Object.keys(stableItemDescriptionSnapshot).length, itemDescriptionMatched, itemDescriptionLowSignal), "npc-classification-map": toCoverage(Object.keys(stableNpcClassificationSnapshot).length, npcClassificationMatched, npcClassificationLowSignal), + "npc-portrait-map": toCoverage( + stableNpcPortraitSnapshots.portraitMap.totalCurrentVbloodRows, + Object.keys(stableNpcPortraitSnapshots.portraitMap.entriesByPrefab).length, + Object.values(stableNpcPortraitSnapshots.candidates.entriesByAssetName).filter((entry) => entry.joinStatus === "circumstantial").length + ), "recipe-link-map": toCoverage(Object.keys(stableRecipeLinkSnapshot).length, recipeLinkMatched, recipeLinkLowSignal) }; @@ -3512,6 +3530,8 @@ async function main() { { fileName: "item-icon-unresolved.json", data: itemIconUnresolvedSnapshot }, { fileName: "item-description-map.json", data: stableItemDescriptionSnapshot }, { fileName: "npc-classification-map.json", data: stableNpcClassificationSnapshot }, + { fileName: "npc-portrait-candidates.json", data: stableNpcPortraitSnapshots.candidates }, + { fileName: "npc-portrait-map.json", data: stableNpcPortraitSnapshots.portraitMap }, { fileName: "recipe-link-map.json", data: stableRecipeLinkSnapshot }, { fileName: "enrichment-coverage.json", data: Object.fromEntries(Object.entries(coverage).sort(([left], [right]) => left.localeCompare(right))) } ]; diff --git a/scripts/validate-generated-data.ts b/scripts/validate-generated-data.ts index 5f6b3f7cd2..ea1754515f 100644 --- a/scripts/validate-generated-data.ts +++ b/scripts/validate-generated-data.ts @@ -1,7 +1,8 @@ import { access, readFile, readdir } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { bloodHuntsSourceKind } from "./blood-hunts"; +import { bloodHuntsSourceKind, nameKeyToLocalizationGuid } from "./blood-hunts"; +import { npcPortraitCandidatesSourceKind, npcPortraitMapSourceKind } from "./npc-portraits"; import { isSafeSlug } from "../src/lib/slug"; import { extractTextVariables, @@ -75,11 +76,13 @@ type BloodHuntsMapEntry = { bloodHuntLevel: number; bloodHuntHideLevel: boolean; nameKey: BloodHuntsNameKey; + nameLocalizationGuid: string; provenance: { sourceKind: string; sourceRef: string; prefabSourceRef: string; localizedNameSourceRef: string; + localizedTextSourceRef: string; npcDisplaySourceRef: string; hideLevelSourceValue: number; }; @@ -97,6 +100,55 @@ type PrefabDisplayMapEntry = { displayNameEn?: string; }; +type NpcClassificationMapEntry = { + guid?: number; + isVBlood?: boolean; + bloodType?: string; +}; + +type NpcPortraitCandidateEntry = { + assetName: string; + assetFamily: string; + assetSourceRefs: string[]; + joinStatus: string; + approvalStatus?: string; + approvalNote?: string; + candidatePrefab?: string; + candidateGuid?: number; + displayNameEn?: string; + evidenceRefs: string[]; + reason: string; +}; + +type NpcPortraitCandidatesSnapshot = { + schemaVersion: number; + sourceKind: string; + sourceRefs: string[]; + totalAssets: number; + currentVbloodRows: number; + entriesByAssetName: Record; +}; + +type NpcPortraitMapEntry = { + prefab: string; + guid: number; + displayNameEn: string; + portraitAssetName: string; + portraitAssetFamily: string; + joinStatus: string; + approvalStatus?: string; + approvalNote?: string; + evidenceRefs: string[]; +}; + +type NpcPortraitMapSnapshot = { + schemaVersion: number; + sourceKind: string; + sourceRef: string; + totalCurrentVbloodRows: number; + entriesByPrefab: Record; +}; + const prefabCategoryParityTargets = [ ["AB", 8255], ["Ability", 13], @@ -302,10 +354,12 @@ async function validateBloodHuntsMap(repoRoot: string): Promise { assert(typeof entry.bloodHuntLevel === "number" && entry.bloodHuntLevel > 0, `${source}: missing positive bloodHuntLevel`); assert(typeof entry.bloodHuntHideLevel === "boolean", `${source}: bloodHuntHideLevel must be boolean`); assertBloodHuntsNameKey(entry.nameKey, source); + assert(entry.nameLocalizationGuid === nameKeyToLocalizationGuid(entry.nameKey), `${source}: nameLocalizationGuid must be derived from nameKey`); assert(entry.provenance?.sourceKind === bloodHuntsSourceKind, `${source}: missing Blood Hunts source provenance`); assert(entry.provenance.sourceRef === bloodHuntsMap.sourceRef, `${source}: entry sourceRef must match map sourceRef`); assert(entry.provenance.prefabSourceRef === "data/prefabs/All.json", `${source}: unexpected prefabSourceRef`); assert(entry.provenance.localizedNameSourceRef === "data/enrichment/prefab-localization.json:namesByGuid", `${source}: unexpected localizedNameSourceRef`); + assert(entry.provenance.localizedTextSourceRef === "Resources/Localization/English.json:Nodes", `${source}: unexpected localizedTextSourceRef`); assert(entry.provenance.npcDisplaySourceRef === "data/enrichment/npc-display-map.json", `${source}: unexpected npcDisplaySourceRef`); assert( Number(entry.provenance.hideLevelSourceValue) === (entry.bloodHuntHideLevel ? 1 : 0), @@ -314,6 +368,68 @@ async function validateBloodHuntsMap(repoRoot: string): Promise { } } +async function validateNpcPortraitMaps(repoRoot: string): Promise { + const candidatesPath = path.join(repoRoot, "data", "enrichment", "npc-portrait-candidates.json"); + const portraitMapPath = path.join(repoRoot, "data", "enrichment", "npc-portrait-map.json"); + const allPrefabsPath = path.join(repoRoot, "data", "prefabs", "All.json"); + const npcDisplayPath = path.join(repoRoot, "data", "enrichment", "npc-display-map.json"); + const npcClassificationPath = path.join(repoRoot, "data", "enrichment", "npc-classification-map.json"); + const bloodHuntsMapPath = path.join(repoRoot, "data", "enrichment", "blood-hunts-map.json"); + + const [candidates, portraitMap, allPrefabs, npcDisplay, npcClassification, bloodHuntsMap] = await Promise.all([ + readJson(candidatesPath), + readJson(portraitMapPath), + readJson>(allPrefabsPath), + readJson>(npcDisplayPath), + readJson>(npcClassificationPath), + readJson(bloodHuntsMapPath) + ]); + const bloodHuntsPrefabs = new Set(Object.values(bloodHuntsMap.entriesByGuid ?? {}).map((entry) => entry.prefab)); + + assert(candidates.schemaVersion === 1, `${candidatesPath}: expected schemaVersion 1`); + assert(candidates.sourceKind === npcPortraitCandidatesSourceKind, `${candidatesPath}: unexpected sourceKind '${candidates.sourceKind}'`); + assert(candidates.totalAssets === Object.keys(candidates.entriesByAssetName ?? {}).length, `${candidatesPath}: totalAssets must match entriesByAssetName count`); + assert(portraitMap.schemaVersion === 1, `${portraitMapPath}: expected schemaVersion 1`); + assert(portraitMap.sourceKind === npcPortraitMapSourceKind, `${portraitMapPath}: unexpected sourceKind '${portraitMap.sourceKind}'`); + assert(portraitMap.sourceRef === "data/enrichment/npc-portrait-candidates.json", `${portraitMapPath}: unexpected sourceRef`); + + const validJoinStatuses = new Set(["source-backed", "user-attested", "circumstantial", "unsafe"]); + const validApprovalStatuses = new Set(["pending", "approved", "rejected"]); + for (const [assetName, entry] of Object.entries(candidates.entriesByAssetName ?? {})) { + const source = `${candidatesPath}:${assetName}`; + assert(entry.assetName === assetName, `${source}: assetName must match map key`); + assert(validJoinStatuses.has(entry.joinStatus), `${source}: invalid joinStatus '${entry.joinStatus}'`); + assert(Array.isArray(entry.assetSourceRefs) && entry.assetSourceRefs.length > 0, `${source}: missing assetSourceRefs`); + assert(Array.isArray(entry.evidenceRefs) && entry.evidenceRefs.length > 0, `${source}: missing evidenceRefs`); + assert(typeof entry.reason === "string" && entry.reason.trim().length > 0, `${source}: missing reason`); + if (entry.joinStatus !== "source-backed") { + assert(Boolean(entry.approvalStatus), `${source}: non-source-backed row must carry approvalStatus`); + assert(validApprovalStatuses.has(entry.approvalStatus ?? ""), `${source}: invalid approvalStatus '${entry.approvalStatus}'`); + } + } + + for (const [prefab, entry] of Object.entries(portraitMap.entriesByPrefab ?? {})) { + const source = `${portraitMapPath}:${prefab}`; + assert(entry.prefab === prefab, `${source}: prefab must match map key`); + assert(entry.joinStatus === "source-backed" || entry.joinStatus === "user-attested", `${source}: unusable joinStatus '${entry.joinStatus}' promoted into portrait map`); + assert(entry.joinStatus !== "user-attested" || entry.approvalStatus === "approved", `${source}: user-attested row must be approved before promotion`); + assert(!/(?:GateBoss|Primal|Minion|Tail|ShadowClone|_UNUSED)/i.test(prefab), `${source}: unsafe prefab variant promoted into portrait map`); + assert(allPrefabs[prefab] === entry.guid, `${source}: prefab '${prefab}' does not join through ${allPrefabsPath}`); + assert(npcDisplay[prefab]?.displayNameEn === entry.displayNameEn, `${source}: displayNameEn does not match ${npcDisplayPath}`); + const classificationEntry = npcClassification[prefab]; + assert( + classificationEntry?.isVBlood === true || classificationEntry?.bloodType === "V Blood" || bloodHuntsPrefabs.has(prefab), + `${source}: portrait map row must join a current V Blood NPC classification or Blood Hunts row` + ); + const candidate = candidates.entriesByAssetName?.[entry.portraitAssetName]; + assert(Boolean(candidate), `${source}: portraitAssetName '${entry.portraitAssetName}' missing from ${candidatesPath}`); + assert(candidate?.candidatePrefab === prefab, `${source}: candidate prefab does not match promoted prefab`); + assert(candidate?.candidateGuid === entry.guid, `${source}: candidate guid does not match promoted guid`); + assert(candidate?.joinStatus === entry.joinStatus, `${source}: candidate joinStatus does not match promoted joinStatus`); + assert(Array.isArray(entry.evidenceRefs) && entry.evidenceRefs.length > 0, `${source}: missing evidenceRefs`); + } +} + function assertNoTextVariables(values: string[] | undefined, source: string, field: string): void { for (const value of values ?? []) { assert(!hasTextVariables(value), `${source}: ${field} '${value}' must not contain unresolved text-variable tokens`); @@ -432,6 +548,7 @@ async function main() { } await validatePrefabCategoryParity(repoRoot, prefabReferenceEntries); await validateBloodHuntsMap(repoRoot); + await validateNpcPortraitMaps(repoRoot); const dbSections = ["items", "recipes", "npcs", "abilities", "workstations", "blueprints", "quests", "buffs", "itemsets"]; const itemIndexBySlug = new Map(); From f133422961927f85e319c8f7271a8b7bbbcfc5bc Mon Sep 17 00:00:00 2001 From: mfoltz Date: Wed, 6 May 2026 11:06:37 -0500 Subject: [PATCH 3/6] Add generated NPC portrait snapshots --- data/enrichment/blood-hunts-map.json | 122 + data/enrichment/coverage-thresholds.json | 5 + data/enrichment/enrichment-coverage.json | 7 + data/enrichment/npc-portrait-candidates.json | 5366 ++++++++++++++++++ data/enrichment/npc-portrait-map.json | 177 + 5 files changed, 5677 insertions(+) create mode 100644 data/enrichment/npc-portrait-candidates.json create mode 100644 data/enrichment/npc-portrait-map.json diff --git a/data/enrichment/blood-hunts-map.json b/data/enrichment/blood-hunts-map.json index c5ac92d0ad..8e65951588 100644 --- a/data/enrichment/blood-hunts-map.json +++ b/data/enrichment/blood-hunts-map.json @@ -15,11 +15,13 @@ "_c": 1804064945, "_d": -492151920 }, + "nameLocalizationGuid": "a9c38cae-4d12-44dd-b1d8-876b905baae2", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -35,11 +37,13 @@ "_c": -1814552402, "_d": 734748326 }, + "nameLocalizationGuid": "59af5689-73bc-49cc-ae20-d893a65ecb2b", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -55,11 +59,13 @@ "_c": 264151730, "_d": 934388860 }, + "nameLocalizationGuid": "4d645623-80e5-4d9e-b2a2-be0f7ca4b137", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -75,11 +81,13 @@ "_c": 1419647660, "_d": 692513366 }, + "nameLocalizationGuid": "de6bdc63-562d-4441-ac1a-9e5456ea4629", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -95,11 +103,13 @@ "_c": 633446831, "_d": -559751700 }, + "nameLocalizationGuid": "0404cf9d-9b94-43e2-afa1-c125ecdda2de", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -115,11 +125,13 @@ "_c": 2033021088, "_d": 1616325332 }, + "nameLocalizationGuid": "53d5aebb-d82b-4a94-a070-2d79d42a5760", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -135,11 +147,13 @@ "_c": 973463213, "_d": 1205737568 }, + "nameLocalizationGuid": "9c7c1ef0-cf92-412a-adde-053a6018de47", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -155,11 +169,13 @@ "_c": 81069982, "_d": -1189635642 }, + "nameLocalizationGuid": "846da9a8-95af-458f-9e07-d504c69917b9", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -175,11 +191,13 @@ "_c": 944169373, "_d": 1884908309 }, + "nameLocalizationGuid": "7e1a0761-1a1a-4766-9de1-4638156b5970", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 1 } @@ -195,11 +213,13 @@ "_c": -1911888210, "_d": 619786419 }, + "nameLocalizationGuid": "d553ccd2-fb35-49dc-aee6-0a8eb330f124", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -215,11 +235,13 @@ "_c": 621028741, "_d": 1037196826 }, + "nameLocalizationGuid": "886eb898-df5a-460d-8525-04251a5ed23d", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -235,11 +257,13 @@ "_c": -1538908028, "_d": 1264759919 }, + "nameLocalizationGuid": "6d8b0943-47ab-49a7-8420-46a46fb4624b", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -255,11 +279,13 @@ "_c": 933608852, "_d": -1003517322 }, + "nameLocalizationGuid": "65bd2953-493b-4699-94bd-a537768a2fc4", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -275,11 +301,13 @@ "_c": 560121523, "_d": -1962654914 }, + "nameLocalizationGuid": "a52b8c47-9562-4119-b3c6-62213e43048b", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -295,11 +323,13 @@ "_c": 1111860414, "_d": -1260104443 }, + "nameLocalizationGuid": "033181b8-c4ce-4ea8-bea4-45420555e4b4", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -315,11 +345,13 @@ "_c": 362143916, "_d": 236122505 }, + "nameLocalizationGuid": "c2517a6c-1fa5-4fb5-ace0-951589f1120e", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -335,11 +367,13 @@ "_c": -704460922, "_d": 1898092421 }, + "nameLocalizationGuid": "bbcdac34-dbd1-4e96-86c7-02d685972271", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -355,11 +389,13 @@ "_c": 2111892637, "_d": -1472772177 }, + "nameLocalizationGuid": "e7644bef-0106-4ee5-9dec-e07daf4737a8", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -375,11 +411,13 @@ "_c": 67269037, "_d": -1165998379 }, + "nameLocalizationGuid": "3b5a9d1b-1f50-4d07-ad71-0204d54680ba", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -395,11 +433,13 @@ "_c": -790933068, "_d": 500706626 }, + "nameLocalizationGuid": "b8977486-8c0f-48dc-b451-dbd0422dd81d", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -415,11 +455,13 @@ "_c": -1476093548, "_d": -628636010 }, + "nameLocalizationGuid": "98b27507-db6e-494b-9499-04a896c687da", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -435,11 +477,13 @@ "_c": -1193367887, "_d": -1405223469 }, + "nameLocalizationGuid": "7fee9abd-d1cc-4a3b-b1a6-deb8d3fd3dac", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -455,11 +499,13 @@ "_c": -1637094469, "_d": 919632434 }, + "nameLocalizationGuid": "850d90b2-fcac-4988-bbeb-6b9e327ad036", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -475,11 +521,13 @@ "_c": -497597035, "_d": -963233357 }, + "nameLocalizationGuid": "590ff5f7-b82f-47cc-9545-57e2b33996c6", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -495,11 +543,13 @@ "_c": -893109087, "_d": 351551282 }, + "nameLocalizationGuid": "3e228167-88a2-410d-a13c-c4ca323ff414", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -515,11 +565,13 @@ "_c": -557893220, "_d": 1538722245 }, + "nameLocalizationGuid": "198d311b-92db-4b67-9c39-bfdec509b75b", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -535,11 +587,13 @@ "_c": -698496105, "_d": -502147021 }, + "nameLocalizationGuid": "83409624-6b4e-4a09-97cb-5dd633d811e2", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -555,11 +609,13 @@ "_c": 708297362, "_d": 690896233 }, + "nameLocalizationGuid": "fec53478-9ae5-46d4-92c2-372a693d2e29", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -575,11 +631,13 @@ "_c": 1882162617, "_d": -1077331044 }, + "nameLocalizationGuid": "1f842f41-ebb2-4e69-b985-2f709c3bc9bf", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -595,11 +653,13 @@ "_c": 257340568, "_d": -1506892857 }, + "nameLocalizationGuid": "69b296b9-b55b-4ef2-98b4-560fc7a32ea6", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -615,11 +675,13 @@ "_c": -114630230, "_d": -1125811300 }, + "nameLocalizationGuid": "9afaaaa5-61e2-4132-aae1-2af99c7be5bc", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -635,11 +697,13 @@ "_c": -2094373215, "_d": -2109230775 }, + "nameLocalizationGuid": "3b0d06aa-66f3-4a99-a166-2a8349b14782", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -655,11 +719,13 @@ "_c": -358455400, "_d": -1142310420 }, + "nameLocalizationGuid": "9163ff1b-1784-461c-9867-a2eaecb9e9bb", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -675,11 +741,13 @@ "_c": -2124586878, "_d": 444398919 }, + "nameLocalizationGuid": "846528ce-861c-4516-8260-5d8147fd7c1a", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -695,11 +763,13 @@ "_c": -190804820, "_d": -1607684816 }, + "nameLocalizationGuid": "a174902c-6321-4470-ac8c-a0f430ad2ca0", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -715,11 +785,13 @@ "_c": 447672245, "_d": 460430756 }, + "nameLocalizationGuid": "fb3e731a-933b-4ff8-b5ef-ae1aa49d711b", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -735,11 +807,13 @@ "_c": 1334740659, "_d": -1271606051 }, + "nameLocalizationGuid": "15da3e2d-6b6b-49d4-b386-8e4fddd434b4", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -755,11 +829,13 @@ "_c": 883242624, "_d": -159229832 }, + "nameLocalizationGuid": "4ec97a89-7b65-4343-8036-a534785882f6", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -775,11 +851,13 @@ "_c": 2056030135, "_d": 559293234 }, + "nameLocalizationGuid": "0d2e3f54-23b5-4ffd-b787-8c7a32235621", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -795,11 +873,13 @@ "_c": -1296973694, "_d": -1659887210 }, + "nameLocalizationGuid": "7f5102ec-fa0d-449d-82c0-b1b29621109d", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -815,11 +895,13 @@ "_c": -1444071292, "_d": -1105947527 }, + "nameLocalizationGuid": "bff27151-9d05-45a5-8438-eda9799414be", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -835,11 +917,13 @@ "_c": -665245024, "_d": 1295866169 }, + "nameLocalizationGuid": "7b7b7478-a726-4b8e-a02a-59d839593d4d", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -855,11 +939,13 @@ "_c": -2006563910, "_d": 2110544887 }, + "nameLocalizationGuid": "e3a817b1-c662-4543-ba43-6688f75bcc7d", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -875,11 +961,13 @@ "_c": 732638651, "_d": -1605286028 }, + "nameLocalizationGuid": "fefef080-eb6a-49e1-bb2d-ab2b744751a0", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -895,11 +983,13 @@ "_c": -1596335979, "_d": 257005169 }, + "nameLocalizationGuid": "aa7475b0-6842-4c09-95d8-d9a07196510f", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -915,11 +1005,13 @@ "_c": -1389443965, "_d": -1361188753 }, + "nameLocalizationGuid": "e3e5021f-a812-495c-83c4-2ead6fe8ddae", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -935,11 +1027,13 @@ "_c": -504822101, "_d": -1380371104 }, + "nameLocalizationGuid": "c5c0bcf8-6a58-457e-ab06-e9e16035b9ad", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -955,11 +1049,13 @@ "_c": -2096246387, "_d": -999299889 }, + "nameLocalizationGuid": "10b52efc-9c24-4f0e-8dd1-0d83cfe46fc4", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -975,11 +1071,13 @@ "_c": -474524779, "_d": -1492731165 }, + "nameLocalizationGuid": "a2fd6eb4-9553-4fa3-9553-b7e3e3ba06a7", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -995,11 +1093,13 @@ "_c": 1470630276, "_d": -1333366017 }, + "nameLocalizationGuid": "1d527ada-ec57-4fd8-8409-a857ff7286b0", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1015,11 +1115,13 @@ "_c": -132458828, "_d": -922935991 }, + "nameLocalizationGuid": "3578461a-547d-4223-b4d6-1af8491dfdc8", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1035,11 +1137,13 @@ "_c": -878458708, "_d": -1729857485 }, + "nameLocalizationGuid": "2fc6d543-b4ef-46fc-acc8-a3cb3378e498", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1055,11 +1159,13 @@ "_c": -1927414123, "_d": 1026705784 }, + "nameLocalizationGuid": "22ffd45f-259a-47a5-95fe-1d8d7849323d", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1075,11 +1181,13 @@ "_c": 195192510, "_d": -961118731 }, + "nameLocalizationGuid": "d7f5b785-a846-4f9e-be66-a20bf57db6c6", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1095,11 +1203,13 @@ "_c": -1392176244, "_d": 482056027 }, + "nameLocalizationGuid": "cb492cc0-5523-4271-8c13-05ad5b97bb1c", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1115,11 +1225,13 @@ "_c": -1849564018, "_d": 84931349 }, + "nameLocalizationGuid": "a7b2b314-aa8d-44a1-8ee4-c19115f30f05", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1135,11 +1247,13 @@ "_c": -1532098674, "_d": -319532786 }, + "nameLocalizationGuid": "d4c423dd-de78-4652-8e07-aea40e51f4ec", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1155,11 +1269,13 @@ "_c": 110004645, "_d": 1930887451 }, + "nameLocalizationGuid": "ba73c4c9-c890-4a32-a589-8e061b011773", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1175,11 +1291,13 @@ "_c": 1097185449, "_d": -1432793934 }, + "nameLocalizationGuid": "460f5eef-29f9-4bab-a9b8-6541b24c99aa", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1195,11 +1313,13 @@ "_c": 1055661737, "_d": -34985969 }, + "nameLocalizationGuid": "df33d822-c589-4c09-a91e-ec3e0f28eafd", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } @@ -1215,11 +1335,13 @@ "_c": 1662961294, "_d": 1259909294 }, + "nameLocalizationGuid": "9277b654-8567-49b5-8ec6-1e63aeb0184b", "provenance": { "sourceKind": "assetripper-monobehaviour-blood-hunts", "sourceRef": "MonoBehaviour/BloodHuntsDataAuthoring.json", "prefabSourceRef": "data/prefabs/All.json", "localizedNameSourceRef": "data/enrichment/prefab-localization.json:namesByGuid", + "localizedTextSourceRef": "Resources/Localization/English.json:Nodes", "npcDisplaySourceRef": "data/enrichment/npc-display-map.json", "hideLevelSourceValue": 0 } diff --git a/data/enrichment/coverage-thresholds.json b/data/enrichment/coverage-thresholds.json index fa3ed587d3..003f60231d 100644 --- a/data/enrichment/coverage-thresholds.json +++ b/data/enrichment/coverage-thresholds.json @@ -36,6 +36,11 @@ "minCoveragePct": 0.4, "warnCoveragePct": 0.9 }, + "npc-portrait-map": { + "minMatchedCount": 0, + "minCoveragePct": 0, + "warnCoveragePct": 0.5 + }, "workstation-display-map": { "minMatchedCount": 120, "minCoveragePct": 0.8, diff --git a/data/enrichment/enrichment-coverage.json b/data/enrichment/enrichment-coverage.json index 9266f4bbbb..a2a69ced8c 100644 --- a/data/enrichment/enrichment-coverage.json +++ b/data/enrichment/enrichment-coverage.json @@ -62,6 +62,13 @@ "signal": "high-signal", "lowSignalExcluded": 596 }, + "npc-portrait-map": { + "total": 69, + "matched": 11, + "coveragePct": 0.1594, + "signal": "high-signal", + "lowSignalExcluded": 182 + }, "quest-display-map": { "total": 163, "matched": 0, diff --git a/data/enrichment/npc-portrait-candidates.json b/data/enrichment/npc-portrait-candidates.json new file mode 100644 index 0000000000..603188c2f0 --- /dev/null +++ b/data/enrichment/npc-portrait-candidates.json @@ -0,0 +1,5366 @@ +{ + "schemaVersion": 1, + "sourceKind": "provisional-vblood-portrait-candidates", + "sourceRefs": [ + "Texture2D", + "Sprite", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "totalAssets": 268, + "currentVbloodRows": 69, + "entriesByAssetName": { + "CHAR_ArchMage_VBlood_HeadPortrait.png": { + "assetName": "CHAR_ArchMage_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_ArchMage_VBlood_HeadPortrait.png", + "Sprite/CHAR_ArchMage_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_ArchMage_VBlood", + "candidateGuid": -2013903325, + "displayNameEn": "Mairwyn the Elementalist", + "evidenceRefs": [ + "Texture2D/CHAR_ArchMage_VBlood_HeadPortrait.png", + "Sprite/CHAR_ArchMage_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Bandit_Bomber_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Bomber_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Bandit_Bomber_VBlood", + "candidateGuid": 1896428751, + "displayNameEn": "Clive the Firestarter", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Bandit_Foreman_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Foreman_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Bandit_Foreman_VBlood", + "candidateGuid": 2122229952, + "displayNameEn": "Rufus the Foreman", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Bandit_Stalker_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Stalker_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Bandit_Stalker_VBlood", + "candidateGuid": 1106149033, + "displayNameEn": "Grayson the Armourer", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Bandit_Tourok_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Tourok_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Bandit_Tourok_VBlood", + "candidateGuid": -1659822956, + "displayNameEn": "Quincey the Bandit King", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_BatVampire_VBlood_HeadPortrait.png": { + "assetName": "CHAR_BatVampire_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_BatVampire_VBlood_HeadPortrait.png", + "Sprite/CHAR_BatVampire_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_BatVampire_VBlood", + "candidateGuid": 1112948824, + "displayNameEn": "Lord Styx the Night Champion", + "evidenceRefs": [ + "Texture2D/CHAR_BatVampire_VBlood_HeadPortrait.png", + "Sprite/CHAR_BatVampire_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Holy_Paladin_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Holy_Paladin_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Holy_Paladin_VBlood_HeadPortrait.png", + "Sprite/CHAR_Holy_Paladin_VBlood_HeadPortrait.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/CHAR_Holy_Paladin_VBlood_HeadPortrait.png", + "Sprite/CHAR_Holy_Paladin_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "asset basename does not join to a current V Blood NPC row" + }, + "CHAR_Militia_Guard_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "Sprite/CHAR_Militia_Guard_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Militia_Guard_VBlood", + "candidateGuid": -29797003, + "displayNameEn": "Vincent the Frostbringer", + "evidenceRefs": [ + "Texture2D/CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "Sprite/CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Militia_Leader_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "Sprite/CHAR_Militia_Leader_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Militia_Leader_VBlood", + "candidateGuid": 1688478381, + "displayNameEn": "Octavian the Militia Captain", + "evidenceRefs": [ + "Texture2D/CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "Sprite/CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "CHAR_Vermin_DireRat_VBlood_HeadPortrait.png": { + "assetName": "CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "assetFamily": "char-vblood-headportrait", + "assetSourceRefs": [ + "Texture2D/CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "Sprite/CHAR_Vermin_DireRat_VBlood_HeadPortrait.png" + ], + "joinStatus": "source-backed", + "candidatePrefab": "CHAR_Vermin_DireRat_VBlood", + "candidateGuid": -2039908510, + "displayNameEn": "Nibbles the Putrid Rat", + "evidenceRefs": [ + "Texture2D/CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "Sprite/CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ], + "reason": "asset basename exactly matches a current V Blood prefab plus HeadPortrait suffix" + }, + "Portrait_Large_Normal_AlphaWolf.png": { + "assetName": "Portrait_Large_Normal_AlphaWolf.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_AlphaWolf.png", + "Sprite/Portrait_Large_Normal_AlphaWolf.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Wolf_VBlood", + "candidateGuid": -1905691330, + "displayNameEn": "Alpha the White Wolf", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_AlphaWolf.png", + "Sprite/Portrait_Large_Normal_AlphaWolf.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_ArenaChampion.png": { + "assetName": "Portrait_Large_Normal_ArenaChampion.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_ArenaChampion.png", + "Sprite/Portrait_Large_Normal_ArenaChampion.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ArenaChampion_VBlood", + "candidateGuid": -753453016, + "displayNameEn": "Gaius the Cursed Champion", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ArenaChampion.png", + "Sprite/Portrait_Large_Normal_ArenaChampion.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_AzarielSunbringer.png": { + "assetName": "Portrait_Large_Normal_AzarielSunbringer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_AzarielSunbringer.png", + "Sprite/Portrait_Large_Normal_AzarielSunbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Cardinal_VBlood", + "candidateGuid": 114912615, + "displayNameEn": "Azariel the Sunbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_AzarielSunbringer.png", + "Sprite/Portrait_Large_Normal_AzarielSunbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_BeatriceTailor.png": { + "assetName": "Portrait_Large_Normal_BeatriceTailor.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_BeatriceTailor.png", + "Sprite/Portrait_Large_Normal_BeatriceTailor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_Tailor_VBlood", + "candidateGuid": -1942352521, + "displayNameEn": "Beatrice the Tailor", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_BeatriceTailor.png", + "Sprite/Portrait_Large_Normal_BeatriceTailor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_BloodCommander.png": { + "assetName": "Portrait_Large_Normal_BloodCommander.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_BloodCommander.png", + "Sprite/Portrait_Large_Normal_BloodCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: BloodCommander is General Valencia the Depraved.", + "candidatePrefab": "CHAR_Vampire_BloodKnight_VBlood", + "candidateGuid": 495971434, + "displayNameEn": "General Valencia the Depraved", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_BloodCommander.png", + "Sprite/Portrait_Large_Normal_BloodCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Large_Normal_ChristinaSunPriestess.png": { + "assetName": "Portrait_Large_Normal_ChristinaSunPriestess.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_ChristinaSunPriestess.png", + "Sprite/Portrait_Large_Normal_ChristinaSunPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Nun_VBlood", + "candidateGuid": -99012450, + "displayNameEn": "Christina the Sun Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ChristinaSunPriestess.png", + "Sprite/Portrait_Large_Normal_ChristinaSunPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_CliveFirestarter.png": { + "assetName": "Portrait_Large_Normal_CliveFirestarter.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_CliveFirestarter.png", + "Sprite/Portrait_Large_Normal_CliveFirestarter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Bomber_VBlood", + "candidateGuid": 1896428751, + "displayNameEn": "Clive the Firestarter", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_CliveFirestarter.png", + "Sprite/Portrait_Large_Normal_CliveFirestarter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_CursedSmith.png": { + "assetName": "Portrait_Large_Normal_CursedSmith.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_CursedSmith.png", + "Sprite/Portrait_Large_Normal_CursedSmith.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_CursedSmith_VBlood", + "candidateGuid": 326378955, + "displayNameEn": "Cyril the Cursed Smith", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_CursedSmith.png", + "Sprite/Portrait_Large_Normal_CursedSmith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_CursedWanderer.png": { + "assetName": "Portrait_Large_Normal_CursedWanderer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_CursedWanderer.png", + "Sprite/Portrait_Large_Normal_CursedWanderer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_CursedWanderer_VBlood", + "candidateGuid": 109969450, + "displayNameEn": "Ben the Old Wanderer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_CursedWanderer.png", + "Sprite/Portrait_Large_Normal_CursedWanderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Dracula.png": { + "assetName": "Portrait_Large_Normal_Dracula.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Dracula.png", + "Sprite/Portrait_Large_Normal_Dracula.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Vampire_Dracula_VBlood", + "candidateGuid": -327335305, + "displayNameEn": "Dracula the Immortal King", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Dracula.png", + "Sprite/Portrait_Large_Normal_Dracula.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_DukeBalaton.png": { + "assetName": "Portrait_Large_Normal_DukeBalaton.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_DukeBalaton.png", + "Sprite/Portrait_Large_Normal_DukeBalaton.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_ToadKing_VBlood", + "candidateGuid": -203043163, + "displayNameEn": "Albert the Duke of Balaton", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_DukeBalaton.png", + "Sprite/Portrait_Large_Normal_DukeBalaton.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_ErrolStonebreaker.png": { + "assetName": "Portrait_Large_Normal_ErrolStonebreaker.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_ErrolStonebreaker.png", + "Sprite/Portrait_Large_Normal_ErrolStonebreaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_StoneBreaker_VBlood", + "candidateGuid": -2025101517, + "displayNameEn": "Errol the Stonebreaker", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ErrolStonebreaker.png", + "Sprite/Portrait_Large_Normal_ErrolStonebreaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Fabian.png": { + "assetName": "Portrait_Large_Normal_Fabian.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Fabian.png", + "Sprite/Portrait_Large_Normal_Fabian.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Fabian.png", + "Sprite/Portrait_Large_Normal_Fabian.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_FerociousBear.png": { + "assetName": "Portrait_Large_Normal_FerociousBear.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_FerociousBear.png", + "Sprite/Portrait_Large_Normal_FerociousBear.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Bear_Dire_Vblood", + "candidateGuid": -1391546313, + "displayNameEn": "Kodia the Ferocious Bear", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FerociousBear.png", + "Sprite/Portrait_Large_Normal_FerociousBear.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Fisherman.png": { + "assetName": "Portrait_Large_Normal_Fisherman.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Fisherman.png", + "Sprite/Portrait_Large_Normal_Fisherman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Fisherman_VBlood", + "candidateGuid": -2122682556, + "displayNameEn": "Finn the Fisherman", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Fisherman.png", + "Sprite/Portrait_Large_Normal_Fisherman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_ForgeBinder.png": { + "assetName": "Portrait_Large_Normal_ForgeBinder.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_ForgeBinder.png", + "Sprite/Portrait_Large_Normal_ForgeBinder.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Blackfang_Valyr_VBlood", + "candidateGuid": 173259239, + "displayNameEn": "Dantos the Forgebinder", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ForgeBinder.png", + "Sprite/Portrait_Large_Normal_ForgeBinder.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_FoulrotSoultaker.png": { + "assetName": "Portrait_Large_Normal_FoulrotSoultaker.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_FoulrotSoultaker.png", + "Sprite/Portrait_Large_Normal_FoulrotSoultaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ZealousCultist_VBlood", + "candidateGuid": -1208888966, + "displayNameEn": "Foulrot the Soultaker", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FoulrotSoultaker.png", + "Sprite/Portrait_Large_Normal_FoulrotSoultaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_FrostCommander.png": { + "assetName": "Portrait_Large_Normal_FrostCommander.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_FrostCommander.png", + "Sprite/Portrait_Large_Normal_FrostCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: FrostCommander is General Elena the Hollow.", + "candidatePrefab": "CHAR_Vampire_IceRanger_VBlood", + "candidateGuid": 795262842, + "displayNameEn": "General Elena the Hollow", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FrostCommander.png", + "Sprite/Portrait_Large_Normal_FrostCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Large_Normal_FrostmawMountainTerror.png": { + "assetName": "Portrait_Large_Normal_FrostmawMountainTerror.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_FrostmawMountainTerror.png", + "Sprite/Portrait_Large_Normal_FrostmawMountainTerror.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Wendigo_VBlood", + "candidateGuid": 24378719, + "displayNameEn": "Frostmaw the Mountain Terror", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FrostmawMountainTerror.png", + "Sprite/Portrait_Large_Normal_FrostmawMountainTerror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_GerardCarver.png": { + "assetName": "Portrait_Large_Normal_GerardCarver.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_GerardCarver.png", + "Sprite/Portrait_Large_Normal_GerardCarver.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_GerardCarver.png", + "Sprite/Portrait_Large_Normal_GerardCarver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_Glassblower.png": { + "assetName": "Portrait_Large_Normal_Glassblower.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Glassblower.png", + "Sprite/Portrait_Large_Normal_Glassblower.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Glassblower_VBlood", + "candidateGuid": 910988233, + "displayNameEn": "Grethel the Glassblower", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Glassblower.png", + "Sprite/Portrait_Large_Normal_Glassblower.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_GorecrusherBehemoth.png": { + "assetName": "Portrait_Large_Normal_GorecrusherBehemoth.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_GorecrusherBehemoth.png", + "Sprite/Portrait_Large_Normal_GorecrusherBehemoth.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_MountainBeast_VBlood", + "candidateGuid": -1936575244, + "displayNameEn": "Gorecrusher the Behemoth", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_GorecrusherBehemoth.png", + "Sprite/Portrait_Large_Normal_GorecrusherBehemoth.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_GoreswineRavager.png": { + "assetName": "Portrait_Large_Normal_GoreswineRavager.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_GoreswineRavager.png", + "Sprite/Portrait_Large_Normal_GoreswineRavager.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfDeath_VBlood", + "candidateGuid": 577478542, + "displayNameEn": "Goreswine the Ravager", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_GoreswineRavager.png", + "Sprite/Portrait_Large_Normal_GoreswineRavager.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_GraysonArmourer.png": { + "assetName": "Portrait_Large_Normal_GraysonArmourer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_GraysonArmourer.png", + "Sprite/Portrait_Large_Normal_GraysonArmourer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Stalker_VBlood", + "candidateGuid": 1106149033, + "displayNameEn": "Grayson the Armourer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_GraysonArmourer.png", + "Sprite/Portrait_Large_Normal_GraysonArmourer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Iva.png": { + "assetName": "Portrait_Large_Normal_Iva.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Iva.png", + "Sprite/Portrait_Large_Normal_Iva.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Iva.png", + "Sprite/Portrait_Large_Normal_Iva.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_JadeVampireHunter.png": { + "assetName": "Portrait_Large_Normal_JadeVampireHunter.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_JadeVampireHunter.png", + "Sprite/Portrait_Large_Normal_JadeVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Jade_VBlood", + "candidateGuid": -1968372384, + "displayNameEn": "Jade the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_JadeVampireHunter.png", + "Sprite/Portrait_Large_Normal_JadeVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_KeelyFrostArcher.png": { + "assetName": "Portrait_Large_Normal_KeelyFrostArcher.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_KeelyFrostArcher.png", + "Sprite/Portrait_Large_Normal_KeelyFrostArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Frostarrow_VBlood", + "candidateGuid": 1124739990, + "displayNameEn": "Keely the Frost Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_KeelyFrostArcher.png", + "Sprite/Portrait_Large_Normal_KeelyFrostArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_LeandraShadowPriestess.png": { + "assetName": "Portrait_Large_Normal_LeandraShadowPriestess.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_LeandraShadowPriestess.png", + "Sprite/Portrait_Large_Normal_LeandraShadowPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfShadows_VBlood", + "candidateGuid": 939467639, + "displayNameEn": "Leandra the Shadow Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_LeandraShadowPriestess.png", + "Sprite/Portrait_Large_Normal_LeandraShadowPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_LidiaChaosArcher.png": { + "assetName": "Portrait_Large_Normal_LidiaChaosArcher.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_LidiaChaosArcher.png", + "Sprite/Portrait_Large_Normal_LidiaChaosArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Chaosarrow_VBlood", + "candidateGuid": 763273073, + "displayNameEn": "Lidia the Chaos Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_LidiaChaosArcher.png", + "Sprite/Portrait_Large_Normal_LidiaChaosArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Livith.png": { + "assetName": "Portrait_Large_Normal_Livith.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Livith.png", + "Sprite/Portrait_Large_Normal_Livith.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Livith.png", + "Sprite/Portrait_Large_Normal_Livith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_Lucie.png": { + "assetName": "Portrait_Large_Normal_Lucie.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Lucie.png", + "Sprite/Portrait_Large_Normal_Lucie.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Lucie.png", + "Sprite/Portrait_Large_Normal_Lucie.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_MairwynElementalist.png": { + "assetName": "Portrait_Large_Normal_MairwynElementalist.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_MairwynElementalist.png", + "Sprite/Portrait_Large_Normal_MairwynElementalist.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ArchMage_VBlood", + "candidateGuid": -2013903325, + "displayNameEn": "Mairwyn the Elementalist", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MairwynElementalist.png", + "Sprite/Portrait_Large_Normal_MairwynElementalist.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_MatkaCurseWeaver.png": { + "assetName": "Portrait_Large_Normal_MatkaCurseWeaver.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_MatkaCurseWeaver.png", + "Sprite/Portrait_Large_Normal_MatkaCurseWeaver.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_Witch_VBlood", + "candidateGuid": -910296704, + "displayNameEn": "Matka the Curse Weaver", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MatkaCurseWeaver.png", + "Sprite/Portrait_Large_Normal_MatkaCurseWeaver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_MeredithBrightArcher.png": { + "assetName": "Portrait_Large_Normal_MeredithBrightArcher.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_MeredithBrightArcher.png", + "Sprite/Portrait_Large_Normal_MeredithBrightArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Longbowman_LightArrow_Vblood", + "candidateGuid": 850622034, + "displayNameEn": "Meredith the Bright Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MeredithBrightArcher.png", + "Sprite/Portrait_Large_Normal_MeredithBrightArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Monster.png": { + "assetName": "Portrait_Large_Normal_Monster.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Monster.png", + "Sprite/Portrait_Large_Normal_Monster.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Monster.png", + "Sprite/Portrait_Large_Normal_Monster.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_Morgana.png": { + "assetName": "Portrait_Large_Normal_Morgana.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Morgana.png", + "Sprite/Portrait_Large_Normal_Morgana.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Morgana.png", + "Sprite/Portrait_Large_Normal_Morgana.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_MorianStormwingMatriarch.png": { + "assetName": "Portrait_Large_Normal_MorianStormwingMatriarch.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_MorianStormwingMatriarch.png", + "Sprite/Portrait_Large_Normal_MorianStormwingMatriarch.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Harpy_Matriarch_VBlood", + "candidateGuid": 685266977, + "displayNameEn": "Morian the Stormwing Matriarch", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MorianStormwingMatriarch.png", + "Sprite/Portrait_Large_Normal_MorianStormwingMatriarch.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_NicholausFallen.png": { + "assetName": "Portrait_Large_Normal_NicholausFallen.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_NicholausFallen.png", + "Sprite/Portrait_Large_Normal_NicholausFallen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Priest_VBlood", + "candidateGuid": 153390636, + "displayNameEn": "Nicholaus the Fallen", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_NicholausFallen.png", + "Sprite/Portrait_Large_Normal_NicholausFallen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_NightmarshalStyxSunderer.png": { + "assetName": "Portrait_Large_Normal_NightmarshalStyxSunderer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Large_Normal_NightmarshalStyxSunderer.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Large_Normal_NightmarshalStyxSunderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_OctavianMilitiaCaptain.png": { + "assetName": "Portrait_Large_Normal_OctavianMilitiaCaptain.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Large_Normal_OctavianMilitiaCaptain.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Leader_VBlood", + "candidateGuid": 1688478381, + "displayNameEn": "Octavian the Militia Captain", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Large_Normal_OctavianMilitiaCaptain.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Overseer.png": { + "assetName": "Portrait_Large_Normal_Overseer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Overseer.png", + "Sprite/Portrait_Large_Normal_Overseer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Overseer_VBlood", + "candidateGuid": -26105228, + "displayNameEn": "Sir Magnus the Overseer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Overseer.png", + "Sprite/Portrait_Large_Normal_Overseer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_PMK01.png": { + "assetName": "Portrait_Large_Normal_PMK01.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_PMK01.png", + "Sprite/Portrait_Large_Normal_PMK01.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_PMK01.png", + "Sprite/Portrait_Large_Normal_PMK01.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_PoloraFeywalker.png": { + "assetName": "Portrait_Large_Normal_PoloraFeywalker.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_PoloraFeywalker.png", + "Sprite/Portrait_Large_Normal_PoloraFeywalker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Poloma_VBlood", + "candidateGuid": -484556888, + "displayNameEn": "Polora the Feywalker", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_PoloraFeywalker.png", + "Sprite/Portrait_Large_Normal_PoloraFeywalker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Professor.png": { + "assetName": "Portrait_Large_Normal_Professor.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Professor.png", + "Sprite/Portrait_Large_Normal_Professor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_TheProfessor_VBlood", + "candidateGuid": 814083983, + "displayNameEn": "Henry Blackbrew the Doctor", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Professor.png", + "Sprite/Portrait_Large_Normal_Professor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Purifier.png": { + "assetName": "Portrait_Large_Normal_Purifier.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Purifier.png", + "Sprite/Portrait_Large_Normal_Purifier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_Purifier_VBlood", + "candidateGuid": 106480588, + "displayNameEn": "Angram the Purifier", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Purifier.png", + "Sprite/Portrait_Large_Normal_Purifier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_PutridRat.png": { + "assetName": "Portrait_Large_Normal_PutridRat.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_PutridRat.png", + "Sprite/Portrait_Large_Normal_PutridRat.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Vermin_DireRat_VBlood", + "candidateGuid": -2039908510, + "displayNameEn": "Nibbles the Putrid Rat", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_PutridRat.png", + "Sprite/Portrait_Large_Normal_PutridRat.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_QuinceyBanditKing.png": { + "assetName": "Portrait_Large_Normal_QuinceyBanditKing.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_QuinceyBanditKing.png", + "Sprite/Portrait_Large_Normal_QuinceyBanditKing.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Tourok_VBlood", + "candidateGuid": -1659822956, + "displayNameEn": "Quincey the Bandit King", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_QuinceyBanditKing.png", + "Sprite/Portrait_Large_Normal_QuinceyBanditKing.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_RazielShepherd.png": { + "assetName": "Portrait_Large_Normal_RazielShepherd.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_RazielShepherd.png", + "Sprite/Portrait_Large_Normal_RazielShepherd.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_BishopOfDunley_VBlood", + "candidateGuid": -680831417, + "displayNameEn": "Raziel the Shepherd", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_RazielShepherd.png", + "Sprite/Portrait_Large_Normal_RazielShepherd.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_RufusForeman.png": { + "assetName": "Portrait_Large_Normal_RufusForeman.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_RufusForeman.png", + "Sprite/Portrait_Large_Normal_RufusForeman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Foreman_VBlood", + "candidateGuid": 2122229952, + "displayNameEn": "Rufus the Foreman", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_RufusForeman.png", + "Sprite/Portrait_Large_Normal_RufusForeman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Scholar.png": { + "assetName": "Portrait_Large_Normal_Scholar.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Scholar.png", + "Sprite/Portrait_Large_Normal_Scholar.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Scholar.png", + "Sprite/Portrait_Large_Normal_Scholar.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_SergeantRailgunner.png": { + "assetName": "Portrait_Large_Normal_SergeantRailgunner.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_SergeantRailgunner.png", + "Sprite/Portrait_Large_Normal_SergeantRailgunner.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_SergeantRailgunner.png", + "Sprite/Portrait_Large_Normal_SergeantRailgunner.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_ShadowInfiltrator.png": { + "assetName": "Portrait_Large_Normal_ShadowInfiltrator.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_ShadowInfiltrator.png", + "Sprite/Portrait_Large_Normal_ShadowInfiltrator.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ShadowInfiltrator.png", + "Sprite/Portrait_Large_Normal_ShadowInfiltrator.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_SolarusImmaculate.png": { + "assetName": "Portrait_Large_Normal_SolarusImmaculate.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_SolarusImmaculate.png", + "Sprite/Portrait_Large_Normal_SolarusImmaculate.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Paladin_VBlood", + "candidateGuid": -740796338, + "displayNameEn": "Solarus the Immaculate", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_SolarusImmaculate.png", + "Sprite/Portrait_Large_Normal_SolarusImmaculate.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Sommelier.png": { + "assetName": "Portrait_Large_Normal_Sommelier.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Sommelier.png", + "Sprite/Portrait_Large_Normal_Sommelier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Sommelier_VBlood", + "candidateGuid": 192051202, + "displayNameEn": "Baron du Bouchon the Sommelier", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Sommelier.png", + "Sprite/Portrait_Large_Normal_Sommelier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_TerahGeomancer.png": { + "assetName": "Portrait_Large_Normal_TerahGeomancer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_TerahGeomancer.png", + "Sprite/Portrait_Large_Normal_TerahGeomancer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Geomancer_Human_VBlood", + "candidateGuid": -1065970933, + "displayNameEn": "Terah the Geomancer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_TerahGeomancer.png", + "Sprite/Portrait_Large_Normal_TerahGeomancer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_TerrorclawOgre.png": { + "assetName": "Portrait_Large_Normal_TerrorclawOgre.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_TerrorclawOgre.png", + "Sprite/Portrait_Large_Normal_TerrorclawOgre.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Winter_Yeti_VBlood", + "candidateGuid": -1347412392, + "displayNameEn": "Terrorclaw the Ogre", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_TerrorclawOgre.png", + "Sprite/Portrait_Large_Normal_TerrorclawOgre.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_TristanVampireHunter.png": { + "assetName": "Portrait_Large_Normal_TristanVampireHunter.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_TristanVampireHunter.png", + "Sprite/Portrait_Large_Normal_TristanVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Leader_VBlood", + "candidateGuid": -1449631170, + "displayNameEn": "Tristan the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_TristanVampireHunter.png", + "Sprite/Portrait_Large_Normal_TristanVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_UndeadGeneral.png": { + "assetName": "Portrait_Large_Normal_UndeadGeneral.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_UndeadGeneral.png", + "Sprite/Portrait_Large_Normal_UndeadGeneral.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Leader_Vblood", + "candidateGuid": -1365931036, + "displayNameEn": "Kriig the Undead General", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_UndeadGeneral.png", + "Sprite/Portrait_Large_Normal_UndeadGeneral.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_UngoraSpiderQueen.png": { + "assetName": "Portrait_Large_Normal_UngoraSpiderQueen.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_UngoraSpiderQueen.png", + "Sprite/Portrait_Large_Normal_UngoraSpiderQueen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Spider_Queen_VBlood", + "candidateGuid": -548489519, + "displayNameEn": "Ungora the Spider Queen", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_UngoraSpiderQueen.png", + "Sprite/Portrait_Large_Normal_UngoraSpiderQueen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_UnholyCommander.png": { + "assetName": "Portrait_Large_Normal_UnholyCommander.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_UnholyCommander.png", + "Sprite/Portrait_Large_Normal_UnholyCommander.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_UnholyCommander.png", + "Sprite/Portrait_Large_Normal_UnholyCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_VincentFrostbringer.png": { + "assetName": "Portrait_Large_Normal_VincentFrostbringer.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_VincentFrostbringer.png", + "Sprite/Portrait_Large_Normal_VincentFrostbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Guard_VBlood", + "candidateGuid": -29797003, + "displayNameEn": "Vincent the Frostbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_VincentFrostbringer.png", + "Sprite/Portrait_Large_Normal_VincentFrostbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Normal_Voltage.png": { + "assetName": "Portrait_Large_Normal_Voltage.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_Voltage.png", + "Sprite/Portrait_Large_Normal_Voltage.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Voltage.png", + "Sprite/Portrait_Large_Normal_Voltage.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_WillfredWerewolfChief.png": { + "assetName": "Portrait_Large_Normal_WillfredWerewolfChief.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_WillfredWerewolfChief.png", + "Sprite/Portrait_Large_Normal_WillfredWerewolfChief.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_WillfredWerewolfChief.png", + "Sprite/Portrait_Large_Normal_WillfredWerewolfChief.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Normal_WingedHorror.png": { + "assetName": "Portrait_Large_Normal_WingedHorror.png", + "assetFamily": "portrait-large-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Normal_WingedHorror.png", + "Sprite/Portrait_Large_Normal_WingedHorror.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Manticore_VBlood", + "candidateGuid": -393555055, + "displayNameEn": "Talzur the Winged Horror", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_WingedHorror.png", + "Sprite/Portrait_Large_Normal_WingedHorror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_AlphaWolf.png": { + "assetName": "Portrait_Large_Smoke_AlphaWolf.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_AlphaWolf.png", + "Sprite/Portrait_Large_Smoke_AlphaWolf.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Wolf_VBlood", + "candidateGuid": -1905691330, + "displayNameEn": "Alpha the White Wolf", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_AlphaWolf.png", + "Sprite/Portrait_Large_Smoke_AlphaWolf.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_ArenaChampion.png": { + "assetName": "Portrait_Large_Smoke_ArenaChampion.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_ArenaChampion.png", + "Sprite/Portrait_Large_Smoke_ArenaChampion.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ArenaChampion_VBlood", + "candidateGuid": -753453016, + "displayNameEn": "Gaius the Cursed Champion", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_ArenaChampion.png", + "Sprite/Portrait_Large_Smoke_ArenaChampion.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_AzarielSunbringer.png": { + "assetName": "Portrait_Large_Smoke_AzarielSunbringer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_AzarielSunbringer.png", + "Sprite/Portrait_Large_Smoke_AzarielSunbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Cardinal_VBlood", + "candidateGuid": 114912615, + "displayNameEn": "Azariel the Sunbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_AzarielSunbringer.png", + "Sprite/Portrait_Large_Smoke_AzarielSunbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_BeatriceTailor.png": { + "assetName": "Portrait_Large_Smoke_BeatriceTailor.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_BeatriceTailor.png", + "Sprite/Portrait_Large_Smoke_BeatriceTailor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_Tailor_VBlood", + "candidateGuid": -1942352521, + "displayNameEn": "Beatrice the Tailor", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_BeatriceTailor.png", + "Sprite/Portrait_Large_Smoke_BeatriceTailor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_BloodCommander.png": { + "assetName": "Portrait_Large_Smoke_BloodCommander.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_BloodCommander.png", + "Sprite/Portrait_Large_Smoke_BloodCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: BloodCommander is General Valencia the Depraved.", + "candidatePrefab": "CHAR_Vampire_BloodKnight_VBlood", + "candidateGuid": 495971434, + "displayNameEn": "General Valencia the Depraved", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_BloodCommander.png", + "Sprite/Portrait_Large_Smoke_BloodCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Large_Smoke_ChristinaSunPriestess.png": { + "assetName": "Portrait_Large_Smoke_ChristinaSunPriestess.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_ChristinaSunPriestess.png", + "Sprite/Portrait_Large_Smoke_ChristinaSunPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Nun_VBlood", + "candidateGuid": -99012450, + "displayNameEn": "Christina the Sun Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_ChristinaSunPriestess.png", + "Sprite/Portrait_Large_Smoke_ChristinaSunPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_CliveFirestarter.png": { + "assetName": "Portrait_Large_Smoke_CliveFirestarter.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_CliveFirestarter.png", + "Sprite/Portrait_Large_Smoke_CliveFirestarter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Bomber_VBlood", + "candidateGuid": 1896428751, + "displayNameEn": "Clive the Firestarter", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_CliveFirestarter.png", + "Sprite/Portrait_Large_Smoke_CliveFirestarter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_CursedSmith.png": { + "assetName": "Portrait_Large_Smoke_CursedSmith.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_CursedSmith.png", + "Sprite/Portrait_Large_Smoke_CursedSmith.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_CursedSmith_VBlood", + "candidateGuid": 326378955, + "displayNameEn": "Cyril the Cursed Smith", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_CursedSmith.png", + "Sprite/Portrait_Large_Smoke_CursedSmith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_CursedWanderer.png": { + "assetName": "Portrait_Large_Smoke_CursedWanderer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_CursedWanderer.png", + "Sprite/Portrait_Large_Smoke_CursedWanderer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_CursedWanderer_VBlood", + "candidateGuid": 109969450, + "displayNameEn": "Ben the Old Wanderer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_CursedWanderer.png", + "Sprite/Portrait_Large_Smoke_CursedWanderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Dracula.png": { + "assetName": "Portrait_Large_Smoke_Dracula.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Dracula.png", + "Sprite/Portrait_Large_Smoke_Dracula.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Vampire_Dracula_VBlood", + "candidateGuid": -327335305, + "displayNameEn": "Dracula the Immortal King", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Dracula.png", + "Sprite/Portrait_Large_Smoke_Dracula.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_DukeBalaton.png": { + "assetName": "Portrait_Large_Smoke_DukeBalaton.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_DukeBalaton.png", + "Sprite/Portrait_Large_Smoke_DukeBalaton.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_ToadKing_VBlood", + "candidateGuid": -203043163, + "displayNameEn": "Albert the Duke of Balaton", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_DukeBalaton.png", + "Sprite/Portrait_Large_Smoke_DukeBalaton.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_ErrolStonebreaker.png": { + "assetName": "Portrait_Large_Smoke_ErrolStonebreaker.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_ErrolStonebreaker.png", + "Sprite/Portrait_Large_Smoke_ErrolStonebreaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_StoneBreaker_VBlood", + "candidateGuid": -2025101517, + "displayNameEn": "Errol the Stonebreaker", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_ErrolStonebreaker.png", + "Sprite/Portrait_Large_Smoke_ErrolStonebreaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Fabian.png": { + "assetName": "Portrait_Large_Smoke_Fabian.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Fabian.png", + "Sprite/Portrait_Large_Smoke_Fabian.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Fabian.png", + "Sprite/Portrait_Large_Smoke_Fabian.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_FerociousBear.png": { + "assetName": "Portrait_Large_Smoke_FerociousBear.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_FerociousBear.png", + "Sprite/Portrait_Large_Smoke_FerociousBear.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Bear_Dire_Vblood", + "candidateGuid": -1391546313, + "displayNameEn": "Kodia the Ferocious Bear", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_FerociousBear.png", + "Sprite/Portrait_Large_Smoke_FerociousBear.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Fisherman.png": { + "assetName": "Portrait_Large_Smoke_Fisherman.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Fisherman.png", + "Sprite/Portrait_Large_Smoke_Fisherman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Fisherman_VBlood", + "candidateGuid": -2122682556, + "displayNameEn": "Finn the Fisherman", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Fisherman.png", + "Sprite/Portrait_Large_Smoke_Fisherman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_ForgeBinder.png": { + "assetName": "Portrait_Large_Smoke_ForgeBinder.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_ForgeBinder.png", + "Sprite/Portrait_Large_Smoke_ForgeBinder.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Blackfang_Valyr_VBlood", + "candidateGuid": 173259239, + "displayNameEn": "Dantos the Forgebinder", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_ForgeBinder.png", + "Sprite/Portrait_Large_Smoke_ForgeBinder.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_FoulrotSoultaker.png": { + "assetName": "Portrait_Large_Smoke_FoulrotSoultaker.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_FoulrotSoultaker.png", + "Sprite/Portrait_Large_Smoke_FoulrotSoultaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ZealousCultist_VBlood", + "candidateGuid": -1208888966, + "displayNameEn": "Foulrot the Soultaker", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_FoulrotSoultaker.png", + "Sprite/Portrait_Large_Smoke_FoulrotSoultaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_FrostCommander.png": { + "assetName": "Portrait_Large_Smoke_FrostCommander.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_FrostCommander.png", + "Sprite/Portrait_Large_Smoke_FrostCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: FrostCommander is General Elena the Hollow.", + "candidatePrefab": "CHAR_Vampire_IceRanger_VBlood", + "candidateGuid": 795262842, + "displayNameEn": "General Elena the Hollow", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_FrostCommander.png", + "Sprite/Portrait_Large_Smoke_FrostCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Large_Smoke_FrostmawMountainTerror.png": { + "assetName": "Portrait_Large_Smoke_FrostmawMountainTerror.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_FrostmawMountainTerror.png", + "Sprite/Portrait_Large_Smoke_FrostmawMountainTerror.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Wendigo_VBlood", + "candidateGuid": 24378719, + "displayNameEn": "Frostmaw the Mountain Terror", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_FrostmawMountainTerror.png", + "Sprite/Portrait_Large_Smoke_FrostmawMountainTerror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_GerardCarver.png": { + "assetName": "Portrait_Large_Smoke_GerardCarver.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_GerardCarver.png", + "Sprite/Portrait_Large_Smoke_GerardCarver.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_GerardCarver.png", + "Sprite/Portrait_Large_Smoke_GerardCarver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_Glassblower.png": { + "assetName": "Portrait_Large_Smoke_Glassblower.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Glassblower.png", + "Sprite/Portrait_Large_Smoke_Glassblower.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Glassblower_VBlood", + "candidateGuid": 910988233, + "displayNameEn": "Grethel the Glassblower", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Glassblower.png", + "Sprite/Portrait_Large_Smoke_Glassblower.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_GorecrusherBehemoth.png": { + "assetName": "Portrait_Large_Smoke_GorecrusherBehemoth.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_GorecrusherBehemoth.png", + "Sprite/Portrait_Large_Smoke_GorecrusherBehemoth.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_MountainBeast_VBlood", + "candidateGuid": -1936575244, + "displayNameEn": "Gorecrusher the Behemoth", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_GorecrusherBehemoth.png", + "Sprite/Portrait_Large_Smoke_GorecrusherBehemoth.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_GoreswineRavager.png": { + "assetName": "Portrait_Large_Smoke_GoreswineRavager.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_GoreswineRavager.png", + "Sprite/Portrait_Large_Smoke_GoreswineRavager.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfDeath_VBlood", + "candidateGuid": 577478542, + "displayNameEn": "Goreswine the Ravager", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_GoreswineRavager.png", + "Sprite/Portrait_Large_Smoke_GoreswineRavager.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_GraysonArmourer.png": { + "assetName": "Portrait_Large_Smoke_GraysonArmourer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_GraysonArmourer.png", + "Sprite/Portrait_Large_Smoke_GraysonArmourer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Stalker_VBlood", + "candidateGuid": 1106149033, + "displayNameEn": "Grayson the Armourer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_GraysonArmourer.png", + "Sprite/Portrait_Large_Smoke_GraysonArmourer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Iva.png": { + "assetName": "Portrait_Large_Smoke_Iva.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Iva.png", + "Sprite/Portrait_Large_Smoke_Iva.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Iva.png", + "Sprite/Portrait_Large_Smoke_Iva.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_JadeVampireHunter.png": { + "assetName": "Portrait_Large_Smoke_JadeVampireHunter.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_JadeVampireHunter.png", + "Sprite/Portrait_Large_Smoke_JadeVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Jade_VBlood", + "candidateGuid": -1968372384, + "displayNameEn": "Jade the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_JadeVampireHunter.png", + "Sprite/Portrait_Large_Smoke_JadeVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_KeelyFrostArcher.png": { + "assetName": "Portrait_Large_Smoke_KeelyFrostArcher.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_KeelyFrostArcher.png", + "Sprite/Portrait_Large_Smoke_KeelyFrostArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Frostarrow_VBlood", + "candidateGuid": 1124739990, + "displayNameEn": "Keely the Frost Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_KeelyFrostArcher.png", + "Sprite/Portrait_Large_Smoke_KeelyFrostArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_LeandraShadowPriestess.png": { + "assetName": "Portrait_Large_Smoke_LeandraShadowPriestess.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_LeandraShadowPriestess.png", + "Sprite/Portrait_Large_Smoke_LeandraShadowPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfShadows_VBlood", + "candidateGuid": 939467639, + "displayNameEn": "Leandra the Shadow Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_LeandraShadowPriestess.png", + "Sprite/Portrait_Large_Smoke_LeandraShadowPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_LidiaChaosArcher.png": { + "assetName": "Portrait_Large_Smoke_LidiaChaosArcher.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_LidiaChaosArcher.png", + "Sprite/Portrait_Large_Smoke_LidiaChaosArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Chaosarrow_VBlood", + "candidateGuid": 763273073, + "displayNameEn": "Lidia the Chaos Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_LidiaChaosArcher.png", + "Sprite/Portrait_Large_Smoke_LidiaChaosArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Livith.png": { + "assetName": "Portrait_Large_Smoke_Livith.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Livith.png", + "Sprite/Portrait_Large_Smoke_Livith.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Livith.png", + "Sprite/Portrait_Large_Smoke_Livith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_Lucie.png": { + "assetName": "Portrait_Large_Smoke_Lucie.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Lucie.png", + "Sprite/Portrait_Large_Smoke_Lucie.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Lucie.png", + "Sprite/Portrait_Large_Smoke_Lucie.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_MairwynElementalist.png": { + "assetName": "Portrait_Large_Smoke_MairwynElementalist.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_MairwynElementalist.png", + "Sprite/Portrait_Large_Smoke_MairwynElementalist.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ArchMage_VBlood", + "candidateGuid": -2013903325, + "displayNameEn": "Mairwyn the Elementalist", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_MairwynElementalist.png", + "Sprite/Portrait_Large_Smoke_MairwynElementalist.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_MatkaCurseWeaver.png": { + "assetName": "Portrait_Large_Smoke_MatkaCurseWeaver.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_MatkaCurseWeaver.png", + "Sprite/Portrait_Large_Smoke_MatkaCurseWeaver.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_Witch_VBlood", + "candidateGuid": -910296704, + "displayNameEn": "Matka the Curse Weaver", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_MatkaCurseWeaver.png", + "Sprite/Portrait_Large_Smoke_MatkaCurseWeaver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_MeredithBrightArcher.png": { + "assetName": "Portrait_Large_Smoke_MeredithBrightArcher.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_MeredithBrightArcher.png", + "Sprite/Portrait_Large_Smoke_MeredithBrightArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Longbowman_LightArrow_Vblood", + "candidateGuid": 850622034, + "displayNameEn": "Meredith the Bright Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_MeredithBrightArcher.png", + "Sprite/Portrait_Large_Smoke_MeredithBrightArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Monster.png": { + "assetName": "Portrait_Large_Smoke_Monster.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Monster.png", + "Sprite/Portrait_Large_Smoke_Monster.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Monster.png", + "Sprite/Portrait_Large_Smoke_Monster.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_Morgana.png": { + "assetName": "Portrait_Large_Smoke_Morgana.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Morgana.png", + "Sprite/Portrait_Large_Smoke_Morgana.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Morgana.png", + "Sprite/Portrait_Large_Smoke_Morgana.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_MorianStormwingMatriarch.png": { + "assetName": "Portrait_Large_Smoke_MorianStormwingMatriarch.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_MorianStormwingMatriarch.png", + "Sprite/Portrait_Large_Smoke_MorianStormwingMatriarch.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Harpy_Matriarch_VBlood", + "candidateGuid": 685266977, + "displayNameEn": "Morian the Stormwing Matriarch", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_MorianStormwingMatriarch.png", + "Sprite/Portrait_Large_Smoke_MorianStormwingMatriarch.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_NicholausFallen.png": { + "assetName": "Portrait_Large_Smoke_NicholausFallen.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_NicholausFallen.png", + "Sprite/Portrait_Large_Smoke_NicholausFallen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Priest_VBlood", + "candidateGuid": 153390636, + "displayNameEn": "Nicholaus the Fallen", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_NicholausFallen.png", + "Sprite/Portrait_Large_Smoke_NicholausFallen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_NightmarshalStyxSunderer.png": { + "assetName": "Portrait_Large_Smoke_NightmarshalStyxSunderer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Large_Smoke_NightmarshalStyxSunderer.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Large_Smoke_NightmarshalStyxSunderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_OctavianMilitiaCaptain.png": { + "assetName": "Portrait_Large_Smoke_OctavianMilitiaCaptain.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Large_Smoke_OctavianMilitiaCaptain.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Leader_VBlood", + "candidateGuid": 1688478381, + "displayNameEn": "Octavian the Militia Captain", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Large_Smoke_OctavianMilitiaCaptain.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Overseer.png": { + "assetName": "Portrait_Large_Smoke_Overseer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Overseer.png", + "Sprite/Portrait_Large_Smoke_Overseer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Overseer_VBlood", + "candidateGuid": -26105228, + "displayNameEn": "Sir Magnus the Overseer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Overseer.png", + "Sprite/Portrait_Large_Smoke_Overseer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_PMK01.png": { + "assetName": "Portrait_Large_Smoke_PMK01.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_PMK01.png", + "Sprite/Portrait_Large_Smoke_PMK01.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_PMK01.png", + "Sprite/Portrait_Large_Smoke_PMK01.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_PoloraFeywalker.png": { + "assetName": "Portrait_Large_Smoke_PoloraFeywalker.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_PoloraFeywalker.png", + "Sprite/Portrait_Large_Smoke_PoloraFeywalker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Poloma_VBlood", + "candidateGuid": -484556888, + "displayNameEn": "Polora the Feywalker", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_PoloraFeywalker.png", + "Sprite/Portrait_Large_Smoke_PoloraFeywalker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Professor.png": { + "assetName": "Portrait_Large_Smoke_Professor.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Professor.png", + "Sprite/Portrait_Large_Smoke_Professor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_TheProfessor_VBlood", + "candidateGuid": 814083983, + "displayNameEn": "Henry Blackbrew the Doctor", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Professor.png", + "Sprite/Portrait_Large_Smoke_Professor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Purifier.png": { + "assetName": "Portrait_Large_Smoke_Purifier.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Purifier.png", + "Sprite/Portrait_Large_Smoke_Purifier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_Purifier_VBlood", + "candidateGuid": 106480588, + "displayNameEn": "Angram the Purifier", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Purifier.png", + "Sprite/Portrait_Large_Smoke_Purifier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_PutridRat.png": { + "assetName": "Portrait_Large_Smoke_PutridRat.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_PutridRat.png", + "Sprite/Portrait_Large_Smoke_PutridRat.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Vermin_DireRat_VBlood", + "candidateGuid": -2039908510, + "displayNameEn": "Nibbles the Putrid Rat", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_PutridRat.png", + "Sprite/Portrait_Large_Smoke_PutridRat.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_QuinceyBanditKing.png": { + "assetName": "Portrait_Large_Smoke_QuinceyBanditKing.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_QuinceyBanditKing.png", + "Sprite/Portrait_Large_Smoke_QuinceyBanditKing.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Tourok_VBlood", + "candidateGuid": -1659822956, + "displayNameEn": "Quincey the Bandit King", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_QuinceyBanditKing.png", + "Sprite/Portrait_Large_Smoke_QuinceyBanditKing.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_RazielShepherd.png": { + "assetName": "Portrait_Large_Smoke_RazielShepherd.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_RazielShepherd.png", + "Sprite/Portrait_Large_Smoke_RazielShepherd.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_BishopOfDunley_VBlood", + "candidateGuid": -680831417, + "displayNameEn": "Raziel the Shepherd", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_RazielShepherd.png", + "Sprite/Portrait_Large_Smoke_RazielShepherd.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_RufusForeman.png": { + "assetName": "Portrait_Large_Smoke_RufusForeman.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_RufusForeman.png", + "Sprite/Portrait_Large_Smoke_RufusForeman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Foreman_VBlood", + "candidateGuid": 2122229952, + "displayNameEn": "Rufus the Foreman", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_RufusForeman.png", + "Sprite/Portrait_Large_Smoke_RufusForeman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Scholar.png": { + "assetName": "Portrait_Large_Smoke_Scholar.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Scholar.png", + "Sprite/Portrait_Large_Smoke_Scholar.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Scholar.png", + "Sprite/Portrait_Large_Smoke_Scholar.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_SergeantRailgunner.png": { + "assetName": "Portrait_Large_Smoke_SergeantRailgunner.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_SergeantRailgunner.png", + "Sprite/Portrait_Large_Smoke_SergeantRailgunner.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_SergeantRailgunner.png", + "Sprite/Portrait_Large_Smoke_SergeantRailgunner.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_ShadowInfiltrator.png": { + "assetName": "Portrait_Large_Smoke_ShadowInfiltrator.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_ShadowInfiltrator.png", + "Sprite/Portrait_Large_Smoke_ShadowInfiltrator.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_ShadowInfiltrator.png", + "Sprite/Portrait_Large_Smoke_ShadowInfiltrator.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_SolarusImmaculate.png": { + "assetName": "Portrait_Large_Smoke_SolarusImmaculate.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_SolarusImmaculate.png", + "Sprite/Portrait_Large_Smoke_SolarusImmaculate.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Paladin_VBlood", + "candidateGuid": -740796338, + "displayNameEn": "Solarus the Immaculate", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_SolarusImmaculate.png", + "Sprite/Portrait_Large_Smoke_SolarusImmaculate.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Sommelier.png": { + "assetName": "Portrait_Large_Smoke_Sommelier.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Sommelier.png", + "Sprite/Portrait_Large_Smoke_Sommelier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Sommelier_VBlood", + "candidateGuid": 192051202, + "displayNameEn": "Baron du Bouchon the Sommelier", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Sommelier.png", + "Sprite/Portrait_Large_Smoke_Sommelier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_TerahGeomancer.png": { + "assetName": "Portrait_Large_Smoke_TerahGeomancer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_TerahGeomancer.png", + "Sprite/Portrait_Large_Smoke_TerahGeomancer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Geomancer_Human_VBlood", + "candidateGuid": -1065970933, + "displayNameEn": "Terah the Geomancer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_TerahGeomancer.png", + "Sprite/Portrait_Large_Smoke_TerahGeomancer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_TerrorclawOgre.png": { + "assetName": "Portrait_Large_Smoke_TerrorclawOgre.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_TerrorclawOgre.png", + "Sprite/Portrait_Large_Smoke_TerrorclawOgre.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Winter_Yeti_VBlood", + "candidateGuid": -1347412392, + "displayNameEn": "Terrorclaw the Ogre", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_TerrorclawOgre.png", + "Sprite/Portrait_Large_Smoke_TerrorclawOgre.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_TristanVampireHunter.png": { + "assetName": "Portrait_Large_Smoke_TristanVampireHunter.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_TristanVampireHunter.png", + "Sprite/Portrait_Large_Smoke_TristanVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Leader_VBlood", + "candidateGuid": -1449631170, + "displayNameEn": "Tristan the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_TristanVampireHunter.png", + "Sprite/Portrait_Large_Smoke_TristanVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_UndeadGeneral.png": { + "assetName": "Portrait_Large_Smoke_UndeadGeneral.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_UndeadGeneral.png", + "Sprite/Portrait_Large_Smoke_UndeadGeneral.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Leader_Vblood", + "candidateGuid": -1365931036, + "displayNameEn": "Kriig the Undead General", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_UndeadGeneral.png", + "Sprite/Portrait_Large_Smoke_UndeadGeneral.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_UngoraSpiderQueen.png": { + "assetName": "Portrait_Large_Smoke_UngoraSpiderQueen.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_UngoraSpiderQueen.png", + "Sprite/Portrait_Large_Smoke_UngoraSpiderQueen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Spider_Queen_VBlood", + "candidateGuid": -548489519, + "displayNameEn": "Ungora the Spider Queen", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_UngoraSpiderQueen.png", + "Sprite/Portrait_Large_Smoke_UngoraSpiderQueen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_UnholyCommander.png": { + "assetName": "Portrait_Large_Smoke_UnholyCommander.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_UnholyCommander.png", + "Sprite/Portrait_Large_Smoke_UnholyCommander.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_UnholyCommander.png", + "Sprite/Portrait_Large_Smoke_UnholyCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_VincentFrostbringer.png": { + "assetName": "Portrait_Large_Smoke_VincentFrostbringer.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_VincentFrostbringer.png", + "Sprite/Portrait_Large_Smoke_VincentFrostbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Guard_VBlood", + "candidateGuid": -29797003, + "displayNameEn": "Vincent the Frostbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_VincentFrostbringer.png", + "Sprite/Portrait_Large_Smoke_VincentFrostbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Large_Smoke_Voltage.png": { + "assetName": "Portrait_Large_Smoke_Voltage.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_Voltage.png", + "Sprite/Portrait_Large_Smoke_Voltage.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_Voltage.png", + "Sprite/Portrait_Large_Smoke_Voltage.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_WillfredWerewolfChief.png": { + "assetName": "Portrait_Large_Smoke_WillfredWerewolfChief.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_WillfredWerewolfChief.png", + "Sprite/Portrait_Large_Smoke_WillfredWerewolfChief.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_WillfredWerewolfChief.png", + "Sprite/Portrait_Large_Smoke_WillfredWerewolfChief.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Large_Smoke_WingedHorror.png": { + "assetName": "Portrait_Large_Smoke_WingedHorror.png", + "assetFamily": "portrait-large-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Large_Smoke_WingedHorror.png", + "Sprite/Portrait_Large_Smoke_WingedHorror.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Manticore_VBlood", + "candidateGuid": -393555055, + "displayNameEn": "Talzur the Winged Horror", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Smoke_WingedHorror.png", + "Sprite/Portrait_Large_Smoke_WingedHorror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_AlphaWolf.png": { + "assetName": "Portrait_Small_Normal_AlphaWolf.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_AlphaWolf.png", + "Sprite/Portrait_Small_Normal_AlphaWolf.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Wolf_VBlood", + "candidateGuid": -1905691330, + "displayNameEn": "Alpha the White Wolf", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_AlphaWolf.png", + "Sprite/Portrait_Small_Normal_AlphaWolf.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_ArenaChampion.png": { + "assetName": "Portrait_Small_Normal_ArenaChampion.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_ArenaChampion.png", + "Sprite/Portrait_Small_Normal_ArenaChampion.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ArenaChampion_VBlood", + "candidateGuid": -753453016, + "displayNameEn": "Gaius the Cursed Champion", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_ArenaChampion.png", + "Sprite/Portrait_Small_Normal_ArenaChampion.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_AzarielSunbringer.png": { + "assetName": "Portrait_Small_Normal_AzarielSunbringer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_AzarielSunbringer.png", + "Sprite/Portrait_Small_Normal_AzarielSunbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Cardinal_VBlood", + "candidateGuid": 114912615, + "displayNameEn": "Azariel the Sunbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_AzarielSunbringer.png", + "Sprite/Portrait_Small_Normal_AzarielSunbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_BeatriceTailor.png": { + "assetName": "Portrait_Small_Normal_BeatriceTailor.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_BeatriceTailor.png", + "Sprite/Portrait_Small_Normal_BeatriceTailor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_Tailor_VBlood", + "candidateGuid": -1942352521, + "displayNameEn": "Beatrice the Tailor", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_BeatriceTailor.png", + "Sprite/Portrait_Small_Normal_BeatriceTailor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_BloodCommander.png": { + "assetName": "Portrait_Small_Normal_BloodCommander.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_BloodCommander.png", + "Sprite/Portrait_Small_Normal_BloodCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: BloodCommander is General Valencia the Depraved.", + "candidatePrefab": "CHAR_Vampire_BloodKnight_VBlood", + "candidateGuid": 495971434, + "displayNameEn": "General Valencia the Depraved", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_BloodCommander.png", + "Sprite/Portrait_Small_Normal_BloodCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Small_Normal_ChristinaSunPriestess.png": { + "assetName": "Portrait_Small_Normal_ChristinaSunPriestess.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_ChristinaSunPriestess.png", + "Sprite/Portrait_Small_Normal_ChristinaSunPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Nun_VBlood", + "candidateGuid": -99012450, + "displayNameEn": "Christina the Sun Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_ChristinaSunPriestess.png", + "Sprite/Portrait_Small_Normal_ChristinaSunPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_CliveFirestarter.png": { + "assetName": "Portrait_Small_Normal_CliveFirestarter.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_CliveFirestarter.png", + "Sprite/Portrait_Small_Normal_CliveFirestarter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Bomber_VBlood", + "candidateGuid": 1896428751, + "displayNameEn": "Clive the Firestarter", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_CliveFirestarter.png", + "Sprite/Portrait_Small_Normal_CliveFirestarter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_CursedSmith.png": { + "assetName": "Portrait_Small_Normal_CursedSmith.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_CursedSmith.png", + "Sprite/Portrait_Small_Normal_CursedSmith.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_CursedSmith_VBlood", + "candidateGuid": 326378955, + "displayNameEn": "Cyril the Cursed Smith", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_CursedSmith.png", + "Sprite/Portrait_Small_Normal_CursedSmith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_CursedWanderer.png": { + "assetName": "Portrait_Small_Normal_CursedWanderer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_CursedWanderer.png", + "Sprite/Portrait_Small_Normal_CursedWanderer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_CursedWanderer_VBlood", + "candidateGuid": 109969450, + "displayNameEn": "Ben the Old Wanderer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_CursedWanderer.png", + "Sprite/Portrait_Small_Normal_CursedWanderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Dracula02.png": { + "assetName": "Portrait_Small_Normal_Dracula02.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Dracula02.png", + "Sprite/Portrait_Small_Normal_Dracula02.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Dracula02.png", + "Sprite/Portrait_Small_Normal_Dracula02.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_DukeBalaton.png": { + "assetName": "Portrait_Small_Normal_DukeBalaton.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_DukeBalaton.png", + "Sprite/Portrait_Small_Normal_DukeBalaton.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_ToadKing_VBlood", + "candidateGuid": -203043163, + "displayNameEn": "Albert the Duke of Balaton", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_DukeBalaton.png", + "Sprite/Portrait_Small_Normal_DukeBalaton.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_ErrolStonebreaker.png": { + "assetName": "Portrait_Small_Normal_ErrolStonebreaker.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_ErrolStonebreaker.png", + "Sprite/Portrait_Small_Normal_ErrolStonebreaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_StoneBreaker_VBlood", + "candidateGuid": -2025101517, + "displayNameEn": "Errol the Stonebreaker", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_ErrolStonebreaker.png", + "Sprite/Portrait_Small_Normal_ErrolStonebreaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Fabian.png": { + "assetName": "Portrait_Small_Normal_Fabian.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Fabian.png", + "Sprite/Portrait_Small_Normal_Fabian.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Fabian.png", + "Sprite/Portrait_Small_Normal_Fabian.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_FerociousBear.png": { + "assetName": "Portrait_Small_Normal_FerociousBear.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_FerociousBear.png", + "Sprite/Portrait_Small_Normal_FerociousBear.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Bear_Dire_Vblood", + "candidateGuid": -1391546313, + "displayNameEn": "Kodia the Ferocious Bear", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_FerociousBear.png", + "Sprite/Portrait_Small_Normal_FerociousBear.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Fisherman.png": { + "assetName": "Portrait_Small_Normal_Fisherman.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Fisherman.png", + "Sprite/Portrait_Small_Normal_Fisherman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Fisherman_VBlood", + "candidateGuid": -2122682556, + "displayNameEn": "Finn the Fisherman", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Fisherman.png", + "Sprite/Portrait_Small_Normal_Fisherman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_ForgeBinder.png": { + "assetName": "Portrait_Small_Normal_ForgeBinder.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_ForgeBinder.png", + "Sprite/Portrait_Small_Normal_ForgeBinder.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Blackfang_Valyr_VBlood", + "candidateGuid": 173259239, + "displayNameEn": "Dantos the Forgebinder", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_ForgeBinder.png", + "Sprite/Portrait_Small_Normal_ForgeBinder.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_FoulrotSoultaker.png": { + "assetName": "Portrait_Small_Normal_FoulrotSoultaker.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_FoulrotSoultaker.png", + "Sprite/Portrait_Small_Normal_FoulrotSoultaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ZealousCultist_VBlood", + "candidateGuid": -1208888966, + "displayNameEn": "Foulrot the Soultaker", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_FoulrotSoultaker.png", + "Sprite/Portrait_Small_Normal_FoulrotSoultaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_FrostCommander.png": { + "assetName": "Portrait_Small_Normal_FrostCommander.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_FrostCommander.png", + "Sprite/Portrait_Small_Normal_FrostCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: FrostCommander is General Elena the Hollow.", + "candidatePrefab": "CHAR_Vampire_IceRanger_VBlood", + "candidateGuid": 795262842, + "displayNameEn": "General Elena the Hollow", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_FrostCommander.png", + "Sprite/Portrait_Small_Normal_FrostCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Small_Normal_FrostmawMountainTerror.png": { + "assetName": "Portrait_Small_Normal_FrostmawMountainTerror.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_FrostmawMountainTerror.png", + "Sprite/Portrait_Small_Normal_FrostmawMountainTerror.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Wendigo_VBlood", + "candidateGuid": 24378719, + "displayNameEn": "Frostmaw the Mountain Terror", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_FrostmawMountainTerror.png", + "Sprite/Portrait_Small_Normal_FrostmawMountainTerror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_GerardCarver.png": { + "assetName": "Portrait_Small_Normal_GerardCarver.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_GerardCarver.png", + "Sprite/Portrait_Small_Normal_GerardCarver.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_GerardCarver.png", + "Sprite/Portrait_Small_Normal_GerardCarver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_Glassblower.png": { + "assetName": "Portrait_Small_Normal_Glassblower.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Glassblower.png", + "Sprite/Portrait_Small_Normal_Glassblower.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Glassblower_VBlood", + "candidateGuid": 910988233, + "displayNameEn": "Grethel the Glassblower", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Glassblower.png", + "Sprite/Portrait_Small_Normal_Glassblower.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_GorecrusherBehemoth2.png": { + "assetName": "Portrait_Small_Normal_GorecrusherBehemoth2.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_GorecrusherBehemoth2.png", + "Sprite/Portrait_Small_Normal_GorecrusherBehemoth2.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_GorecrusherBehemoth2.png", + "Sprite/Portrait_Small_Normal_GorecrusherBehemoth2.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_GoreswineRavager.png": { + "assetName": "Portrait_Small_Normal_GoreswineRavager.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_GoreswineRavager.png", + "Sprite/Portrait_Small_Normal_GoreswineRavager.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfDeath_VBlood", + "candidateGuid": 577478542, + "displayNameEn": "Goreswine the Ravager", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_GoreswineRavager.png", + "Sprite/Portrait_Small_Normal_GoreswineRavager.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_GraysonArmourer.png": { + "assetName": "Portrait_Small_Normal_GraysonArmourer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_GraysonArmourer.png", + "Sprite/Portrait_Small_Normal_GraysonArmourer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Stalker_VBlood", + "candidateGuid": 1106149033, + "displayNameEn": "Grayson the Armourer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_GraysonArmourer.png", + "Sprite/Portrait_Small_Normal_GraysonArmourer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Iva.png": { + "assetName": "Portrait_Small_Normal_Iva.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Iva.png", + "Sprite/Portrait_Small_Normal_Iva.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Iva.png", + "Sprite/Portrait_Small_Normal_Iva.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_JadeVampireHunter.png": { + "assetName": "Portrait_Small_Normal_JadeVampireHunter.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_JadeVampireHunter.png", + "Sprite/Portrait_Small_Normal_JadeVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Jade_VBlood", + "candidateGuid": -1968372384, + "displayNameEn": "Jade the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_JadeVampireHunter.png", + "Sprite/Portrait_Small_Normal_JadeVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_KeelyFrostArcher.png": { + "assetName": "Portrait_Small_Normal_KeelyFrostArcher.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_KeelyFrostArcher.png", + "Sprite/Portrait_Small_Normal_KeelyFrostArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Frostarrow_VBlood", + "candidateGuid": 1124739990, + "displayNameEn": "Keely the Frost Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_KeelyFrostArcher.png", + "Sprite/Portrait_Small_Normal_KeelyFrostArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_LeandraShadowPriestess.png": { + "assetName": "Portrait_Small_Normal_LeandraShadowPriestess.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_LeandraShadowPriestess.png", + "Sprite/Portrait_Small_Normal_LeandraShadowPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfShadows_VBlood", + "candidateGuid": 939467639, + "displayNameEn": "Leandra the Shadow Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_LeandraShadowPriestess.png", + "Sprite/Portrait_Small_Normal_LeandraShadowPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_LidiaChaosArcher.png": { + "assetName": "Portrait_Small_Normal_LidiaChaosArcher.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_LidiaChaosArcher.png", + "Sprite/Portrait_Small_Normal_LidiaChaosArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Chaosarrow_VBlood", + "candidateGuid": 763273073, + "displayNameEn": "Lidia the Chaos Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_LidiaChaosArcher.png", + "Sprite/Portrait_Small_Normal_LidiaChaosArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Livith.png": { + "assetName": "Portrait_Small_Normal_Livith.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Livith.png", + "Sprite/Portrait_Small_Normal_Livith.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Livith.png", + "Sprite/Portrait_Small_Normal_Livith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_Lucie.png": { + "assetName": "Portrait_Small_Normal_Lucie.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Lucie.png", + "Sprite/Portrait_Small_Normal_Lucie.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Lucie.png", + "Sprite/Portrait_Small_Normal_Lucie.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_MairwynElementalist.png": { + "assetName": "Portrait_Small_Normal_MairwynElementalist.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_MairwynElementalist.png", + "Sprite/Portrait_Small_Normal_MairwynElementalist.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ArchMage_VBlood", + "candidateGuid": -2013903325, + "displayNameEn": "Mairwyn the Elementalist", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_MairwynElementalist.png", + "Sprite/Portrait_Small_Normal_MairwynElementalist.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_MatkaCurseWeaver.png": { + "assetName": "Portrait_Small_Normal_MatkaCurseWeaver.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_MatkaCurseWeaver.png", + "Sprite/Portrait_Small_Normal_MatkaCurseWeaver.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_Witch_VBlood", + "candidateGuid": -910296704, + "displayNameEn": "Matka the Curse Weaver", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_MatkaCurseWeaver.png", + "Sprite/Portrait_Small_Normal_MatkaCurseWeaver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_MeredithBrightArcher.png": { + "assetName": "Portrait_Small_Normal_MeredithBrightArcher.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_MeredithBrightArcher.png", + "Sprite/Portrait_Small_Normal_MeredithBrightArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Longbowman_LightArrow_Vblood", + "candidateGuid": 850622034, + "displayNameEn": "Meredith the Bright Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_MeredithBrightArcher.png", + "Sprite/Portrait_Small_Normal_MeredithBrightArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Monster2.png": { + "assetName": "Portrait_Small_Normal_Monster2.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Monster2.png", + "Sprite/Portrait_Small_Normal_Monster2.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Monster2.png", + "Sprite/Portrait_Small_Normal_Monster2.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_Morgana.png": { + "assetName": "Portrait_Small_Normal_Morgana.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Morgana.png", + "Sprite/Portrait_Small_Normal_Morgana.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Morgana.png", + "Sprite/Portrait_Small_Normal_Morgana.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_MorianStormwingMatriarch.png": { + "assetName": "Portrait_Small_Normal_MorianStormwingMatriarch.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_MorianStormwingMatriarch.png", + "Sprite/Portrait_Small_Normal_MorianStormwingMatriarch.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Harpy_Matriarch_VBlood", + "candidateGuid": 685266977, + "displayNameEn": "Morian the Stormwing Matriarch", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_MorianStormwingMatriarch.png", + "Sprite/Portrait_Small_Normal_MorianStormwingMatriarch.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_NicholausFallen.png": { + "assetName": "Portrait_Small_Normal_NicholausFallen.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_NicholausFallen.png", + "Sprite/Portrait_Small_Normal_NicholausFallen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Priest_VBlood", + "candidateGuid": 153390636, + "displayNameEn": "Nicholaus the Fallen", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_NicholausFallen.png", + "Sprite/Portrait_Small_Normal_NicholausFallen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_NightmarshalStyxSunderer.png": { + "assetName": "Portrait_Small_Normal_NightmarshalStyxSunderer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Small_Normal_NightmarshalStyxSunderer.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Small_Normal_NightmarshalStyxSunderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_OctavianMilitiaCaptain.png": { + "assetName": "Portrait_Small_Normal_OctavianMilitiaCaptain.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Small_Normal_OctavianMilitiaCaptain.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Leader_VBlood", + "candidateGuid": 1688478381, + "displayNameEn": "Octavian the Militia Captain", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Small_Normal_OctavianMilitiaCaptain.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Overseer.png": { + "assetName": "Portrait_Small_Normal_Overseer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Overseer.png", + "Sprite/Portrait_Small_Normal_Overseer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Overseer_VBlood", + "candidateGuid": -26105228, + "displayNameEn": "Sir Magnus the Overseer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Overseer.png", + "Sprite/Portrait_Small_Normal_Overseer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_PMK01.png": { + "assetName": "Portrait_Small_Normal_PMK01.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_PMK01.png", + "Sprite/Portrait_Small_Normal_PMK01.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_PMK01.png", + "Sprite/Portrait_Small_Normal_PMK01.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_PoloraFeywalker.png": { + "assetName": "Portrait_Small_Normal_PoloraFeywalker.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_PoloraFeywalker.png", + "Sprite/Portrait_Small_Normal_PoloraFeywalker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Poloma_VBlood", + "candidateGuid": -484556888, + "displayNameEn": "Polora the Feywalker", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_PoloraFeywalker.png", + "Sprite/Portrait_Small_Normal_PoloraFeywalker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Professor.png": { + "assetName": "Portrait_Small_Normal_Professor.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Professor.png", + "Sprite/Portrait_Small_Normal_Professor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_TheProfessor_VBlood", + "candidateGuid": 814083983, + "displayNameEn": "Henry Blackbrew the Doctor", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Professor.png", + "Sprite/Portrait_Small_Normal_Professor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Purifier.png": { + "assetName": "Portrait_Small_Normal_Purifier.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Purifier.png", + "Sprite/Portrait_Small_Normal_Purifier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_Purifier_VBlood", + "candidateGuid": 106480588, + "displayNameEn": "Angram the Purifier", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Purifier.png", + "Sprite/Portrait_Small_Normal_Purifier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_PutridRat.png": { + "assetName": "Portrait_Small_Normal_PutridRat.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_PutridRat.png", + "Sprite/Portrait_Small_Normal_PutridRat.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Vermin_DireRat_VBlood", + "candidateGuid": -2039908510, + "displayNameEn": "Nibbles the Putrid Rat", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_PutridRat.png", + "Sprite/Portrait_Small_Normal_PutridRat.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_QuinceyBanditKing.png": { + "assetName": "Portrait_Small_Normal_QuinceyBanditKing.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_QuinceyBanditKing.png", + "Sprite/Portrait_Small_Normal_QuinceyBanditKing.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Tourok_VBlood", + "candidateGuid": -1659822956, + "displayNameEn": "Quincey the Bandit King", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_QuinceyBanditKing.png", + "Sprite/Portrait_Small_Normal_QuinceyBanditKing.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_RazielShepherd.png": { + "assetName": "Portrait_Small_Normal_RazielShepherd.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_RazielShepherd.png", + "Sprite/Portrait_Small_Normal_RazielShepherd.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_BishopOfDunley_VBlood", + "candidateGuid": -680831417, + "displayNameEn": "Raziel the Shepherd", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_RazielShepherd.png", + "Sprite/Portrait_Small_Normal_RazielShepherd.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_RufusForeman.png": { + "assetName": "Portrait_Small_Normal_RufusForeman.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_RufusForeman.png", + "Sprite/Portrait_Small_Normal_RufusForeman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Foreman_VBlood", + "candidateGuid": 2122229952, + "displayNameEn": "Rufus the Foreman", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_RufusForeman.png", + "Sprite/Portrait_Small_Normal_RufusForeman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Scholar.png": { + "assetName": "Portrait_Small_Normal_Scholar.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Scholar.png", + "Sprite/Portrait_Small_Normal_Scholar.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Scholar.png", + "Sprite/Portrait_Small_Normal_Scholar.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_SergeantRailgunner.png": { + "assetName": "Portrait_Small_Normal_SergeantRailgunner.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_SergeantRailgunner.png", + "Sprite/Portrait_Small_Normal_SergeantRailgunner.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_SergeantRailgunner.png", + "Sprite/Portrait_Small_Normal_SergeantRailgunner.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_ShadowInfiltrator.png": { + "assetName": "Portrait_Small_Normal_ShadowInfiltrator.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_ShadowInfiltrator.png", + "Sprite/Portrait_Small_Normal_ShadowInfiltrator.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_ShadowInfiltrator.png", + "Sprite/Portrait_Small_Normal_ShadowInfiltrator.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_SolarusImmaculate.png": { + "assetName": "Portrait_Small_Normal_SolarusImmaculate.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_SolarusImmaculate.png", + "Sprite/Portrait_Small_Normal_SolarusImmaculate.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Paladin_VBlood", + "candidateGuid": -740796338, + "displayNameEn": "Solarus the Immaculate", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_SolarusImmaculate.png", + "Sprite/Portrait_Small_Normal_SolarusImmaculate.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Sommelier.png": { + "assetName": "Portrait_Small_Normal_Sommelier.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Sommelier.png", + "Sprite/Portrait_Small_Normal_Sommelier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Sommelier_VBlood", + "candidateGuid": 192051202, + "displayNameEn": "Baron du Bouchon the Sommelier", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Sommelier.png", + "Sprite/Portrait_Small_Normal_Sommelier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_TerahGeomancer.png": { + "assetName": "Portrait_Small_Normal_TerahGeomancer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_TerahGeomancer.png", + "Sprite/Portrait_Small_Normal_TerahGeomancer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Geomancer_Human_VBlood", + "candidateGuid": -1065970933, + "displayNameEn": "Terah the Geomancer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_TerahGeomancer.png", + "Sprite/Portrait_Small_Normal_TerahGeomancer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_TerrorclawOgre.png": { + "assetName": "Portrait_Small_Normal_TerrorclawOgre.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_TerrorclawOgre.png", + "Sprite/Portrait_Small_Normal_TerrorclawOgre.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Winter_Yeti_VBlood", + "candidateGuid": -1347412392, + "displayNameEn": "Terrorclaw the Ogre", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_TerrorclawOgre.png", + "Sprite/Portrait_Small_Normal_TerrorclawOgre.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_TristanVampireHunter.png": { + "assetName": "Portrait_Small_Normal_TristanVampireHunter.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_TristanVampireHunter.png", + "Sprite/Portrait_Small_Normal_TristanVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Leader_VBlood", + "candidateGuid": -1449631170, + "displayNameEn": "Tristan the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_TristanVampireHunter.png", + "Sprite/Portrait_Small_Normal_TristanVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_UndeadGeneral.png": { + "assetName": "Portrait_Small_Normal_UndeadGeneral.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_UndeadGeneral.png", + "Sprite/Portrait_Small_Normal_UndeadGeneral.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Leader_Vblood", + "candidateGuid": -1365931036, + "displayNameEn": "Kriig the Undead General", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_UndeadGeneral.png", + "Sprite/Portrait_Small_Normal_UndeadGeneral.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_UngoraSpiderQueen.png": { + "assetName": "Portrait_Small_Normal_UngoraSpiderQueen.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_UngoraSpiderQueen.png", + "Sprite/Portrait_Small_Normal_UngoraSpiderQueen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Spider_Queen_VBlood", + "candidateGuid": -548489519, + "displayNameEn": "Ungora the Spider Queen", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_UngoraSpiderQueen.png", + "Sprite/Portrait_Small_Normal_UngoraSpiderQueen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_UnholyCommander.png": { + "assetName": "Portrait_Small_Normal_UnholyCommander.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_UnholyCommander.png", + "Sprite/Portrait_Small_Normal_UnholyCommander.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_UnholyCommander.png", + "Sprite/Portrait_Small_Normal_UnholyCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_VincentFrostbringer.png": { + "assetName": "Portrait_Small_Normal_VincentFrostbringer.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_VincentFrostbringer.png", + "Sprite/Portrait_Small_Normal_VincentFrostbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Guard_VBlood", + "candidateGuid": -29797003, + "displayNameEn": "Vincent the Frostbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_VincentFrostbringer.png", + "Sprite/Portrait_Small_Normal_VincentFrostbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Normal_Voltage.png": { + "assetName": "Portrait_Small_Normal_Voltage.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_Voltage.png", + "Sprite/Portrait_Small_Normal_Voltage.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_Voltage.png", + "Sprite/Portrait_Small_Normal_Voltage.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_WillfredWerewolfChief.png": { + "assetName": "Portrait_Small_Normal_WillfredWerewolfChief.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_WillfredWerewolfChief.png", + "Sprite/Portrait_Small_Normal_WillfredWerewolfChief.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_WillfredWerewolfChief.png", + "Sprite/Portrait_Small_Normal_WillfredWerewolfChief.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Normal_WingedHorror2.png": { + "assetName": "Portrait_Small_Normal_WingedHorror2.png", + "assetFamily": "portrait-small-normal", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Normal_WingedHorror2.png", + "Sprite/Portrait_Small_Normal_WingedHorror2.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Normal_WingedHorror2.png", + "Sprite/Portrait_Small_Normal_WingedHorror2.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_AlphaWolf.png": { + "assetName": "Portrait_Small_Smoke_AlphaWolf.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_AlphaWolf.png", + "Sprite/Portrait_Small_Smoke_AlphaWolf.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Wolf_VBlood", + "candidateGuid": -1905691330, + "displayNameEn": "Alpha the White Wolf", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_AlphaWolf.png", + "Sprite/Portrait_Small_Smoke_AlphaWolf.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_ArenaChampion.png": { + "assetName": "Portrait_Small_Smoke_ArenaChampion.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_ArenaChampion.png", + "Sprite/Portrait_Small_Smoke_ArenaChampion.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ArenaChampion_VBlood", + "candidateGuid": -753453016, + "displayNameEn": "Gaius the Cursed Champion", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_ArenaChampion.png", + "Sprite/Portrait_Small_Smoke_ArenaChampion.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_AzarielSunbringer.png": { + "assetName": "Portrait_Small_Smoke_AzarielSunbringer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_AzarielSunbringer.png", + "Sprite/Portrait_Small_Smoke_AzarielSunbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Cardinal_VBlood", + "candidateGuid": 114912615, + "displayNameEn": "Azariel the Sunbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_AzarielSunbringer.png", + "Sprite/Portrait_Small_Smoke_AzarielSunbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_BeatriceTailor.png": { + "assetName": "Portrait_Small_Smoke_BeatriceTailor.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_BeatriceTailor.png", + "Sprite/Portrait_Small_Smoke_BeatriceTailor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_Tailor_VBlood", + "candidateGuid": -1942352521, + "displayNameEn": "Beatrice the Tailor", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_BeatriceTailor.png", + "Sprite/Portrait_Small_Smoke_BeatriceTailor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_BloodCommander.png": { + "assetName": "Portrait_Small_Smoke_BloodCommander.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_BloodCommander.png", + "Sprite/Portrait_Small_Smoke_BloodCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: BloodCommander is General Valencia the Depraved.", + "candidatePrefab": "CHAR_Vampire_BloodKnight_VBlood", + "candidateGuid": 495971434, + "displayNameEn": "General Valencia the Depraved", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_BloodCommander.png", + "Sprite/Portrait_Small_Smoke_BloodCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Small_Smoke_ChristinaSunPriestess.png": { + "assetName": "Portrait_Small_Smoke_ChristinaSunPriestess.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_ChristinaSunPriestess.png", + "Sprite/Portrait_Small_Smoke_ChristinaSunPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Nun_VBlood", + "candidateGuid": -99012450, + "displayNameEn": "Christina the Sun Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_ChristinaSunPriestess.png", + "Sprite/Portrait_Small_Smoke_ChristinaSunPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_CliveFirestarter.png": { + "assetName": "Portrait_Small_Smoke_CliveFirestarter.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_CliveFirestarter.png", + "Sprite/Portrait_Small_Smoke_CliveFirestarter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Bomber_VBlood", + "candidateGuid": 1896428751, + "displayNameEn": "Clive the Firestarter", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_CliveFirestarter.png", + "Sprite/Portrait_Small_Smoke_CliveFirestarter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_CursedSmith.png": { + "assetName": "Portrait_Small_Smoke_CursedSmith.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_CursedSmith.png", + "Sprite/Portrait_Small_Smoke_CursedSmith.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_CursedSmith_VBlood", + "candidateGuid": 326378955, + "displayNameEn": "Cyril the Cursed Smith", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_CursedSmith.png", + "Sprite/Portrait_Small_Smoke_CursedSmith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_CursedWanderer.png": { + "assetName": "Portrait_Small_Smoke_CursedWanderer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_CursedWanderer.png", + "Sprite/Portrait_Small_Smoke_CursedWanderer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Villager_CursedWanderer_VBlood", + "candidateGuid": 109969450, + "displayNameEn": "Ben the Old Wanderer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_CursedWanderer.png", + "Sprite/Portrait_Small_Smoke_CursedWanderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Dracula_Grey.png": { + "assetName": "Portrait_Small_Smoke_Dracula_Grey.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Dracula_Grey.png", + "Sprite/Portrait_Small_Smoke_Dracula_Grey.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Dracula_Grey.png", + "Sprite/Portrait_Small_Smoke_Dracula_Grey.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_Dracula02.png": { + "assetName": "Portrait_Small_Smoke_Dracula02.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Dracula02.png", + "Sprite/Portrait_Small_Smoke_Dracula02.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Dracula02.png", + "Sprite/Portrait_Small_Smoke_Dracula02.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_DukeBalaton.png": { + "assetName": "Portrait_Small_Smoke_DukeBalaton.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_DukeBalaton.png", + "Sprite/Portrait_Small_Smoke_DukeBalaton.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_ToadKing_VBlood", + "candidateGuid": -203043163, + "displayNameEn": "Albert the Duke of Balaton", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_DukeBalaton.png", + "Sprite/Portrait_Small_Smoke_DukeBalaton.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_ErrolStonebreaker.png": { + "assetName": "Portrait_Small_Smoke_ErrolStonebreaker.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_ErrolStonebreaker.png", + "Sprite/Portrait_Small_Smoke_ErrolStonebreaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_StoneBreaker_VBlood", + "candidateGuid": -2025101517, + "displayNameEn": "Errol the Stonebreaker", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_ErrolStonebreaker.png", + "Sprite/Portrait_Small_Smoke_ErrolStonebreaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Fabian.png": { + "assetName": "Portrait_Small_Smoke_Fabian.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Fabian.png", + "Sprite/Portrait_Small_Smoke_Fabian.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Fabian.png", + "Sprite/Portrait_Small_Smoke_Fabian.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_FerociousBear.png": { + "assetName": "Portrait_Small_Smoke_FerociousBear.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_FerociousBear.png", + "Sprite/Portrait_Small_Smoke_FerociousBear.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Forest_Bear_Dire_Vblood", + "candidateGuid": -1391546313, + "displayNameEn": "Kodia the Ferocious Bear", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_FerociousBear.png", + "Sprite/Portrait_Small_Smoke_FerociousBear.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Fisherman.png": { + "assetName": "Portrait_Small_Smoke_Fisherman.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Fisherman.png", + "Sprite/Portrait_Small_Smoke_Fisherman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Fisherman_VBlood", + "candidateGuid": -2122682556, + "displayNameEn": "Finn the Fisherman", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Fisherman.png", + "Sprite/Portrait_Small_Smoke_Fisherman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_ForgeBinder.png": { + "assetName": "Portrait_Small_Smoke_ForgeBinder.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_ForgeBinder.png", + "Sprite/Portrait_Small_Smoke_ForgeBinder.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Blackfang_Valyr_VBlood", + "candidateGuid": 173259239, + "displayNameEn": "Dantos the Forgebinder", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_ForgeBinder.png", + "Sprite/Portrait_Small_Smoke_ForgeBinder.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_FoulrotSoultaker.png": { + "assetName": "Portrait_Small_Smoke_FoulrotSoultaker.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_FoulrotSoultaker.png", + "Sprite/Portrait_Small_Smoke_FoulrotSoultaker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_ZealousCultist_VBlood", + "candidateGuid": -1208888966, + "displayNameEn": "Foulrot the Soultaker", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_FoulrotSoultaker.png", + "Sprite/Portrait_Small_Smoke_FoulrotSoultaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_FrostCommander.png": { + "assetName": "Portrait_Small_Smoke_FrostCommander.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_FrostCommander.png", + "Sprite/Portrait_Small_Smoke_FrostCommander.png" + ], + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: FrostCommander is General Elena the Hollow.", + "candidatePrefab": "CHAR_Vampire_IceRanger_VBlood", + "candidateGuid": 795262842, + "displayNameEn": "General Elena the Hollow", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_FrostCommander.png", + "Sprite/Portrait_Small_Smoke_FrostCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "approved user-attested portrait alias" + }, + "Portrait_Small_Smoke_FrostmawMountainTerror.png": { + "assetName": "Portrait_Small_Smoke_FrostmawMountainTerror.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_FrostmawMountainTerror.png", + "Sprite/Portrait_Small_Smoke_FrostmawMountainTerror.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Wendigo_VBlood", + "candidateGuid": 24378719, + "displayNameEn": "Frostmaw the Mountain Terror", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_FrostmawMountainTerror.png", + "Sprite/Portrait_Small_Smoke_FrostmawMountainTerror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_GerardCarver.png": { + "assetName": "Portrait_Small_Smoke_GerardCarver.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_GerardCarver.png", + "Sprite/Portrait_Small_Smoke_GerardCarver.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_GerardCarver.png", + "Sprite/Portrait_Small_Smoke_GerardCarver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_Glassblower.png": { + "assetName": "Portrait_Small_Smoke_Glassblower.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Glassblower.png", + "Sprite/Portrait_Small_Smoke_Glassblower.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Glassblower_VBlood", + "candidateGuid": 910988233, + "displayNameEn": "Grethel the Glassblower", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Glassblower.png", + "Sprite/Portrait_Small_Smoke_Glassblower.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_GorecrusherBehemoth2.png": { + "assetName": "Portrait_Small_Smoke_GorecrusherBehemoth2.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_GorecrusherBehemoth2.png", + "Sprite/Portrait_Small_Smoke_GorecrusherBehemoth2.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_GorecrusherBehemoth2.png", + "Sprite/Portrait_Small_Smoke_GorecrusherBehemoth2.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_GoreswineRavager.png": { + "assetName": "Portrait_Small_Smoke_GoreswineRavager.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_GoreswineRavager.png", + "Sprite/Portrait_Small_Smoke_GoreswineRavager.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfDeath_VBlood", + "candidateGuid": 577478542, + "displayNameEn": "Goreswine the Ravager", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_GoreswineRavager.png", + "Sprite/Portrait_Small_Smoke_GoreswineRavager.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_GraysonArmourer.png": { + "assetName": "Portrait_Small_Smoke_GraysonArmourer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_GraysonArmourer.png", + "Sprite/Portrait_Small_Smoke_GraysonArmourer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Stalker_VBlood", + "candidateGuid": 1106149033, + "displayNameEn": "Grayson the Armourer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_GraysonArmourer.png", + "Sprite/Portrait_Small_Smoke_GraysonArmourer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Iva.png": { + "assetName": "Portrait_Small_Smoke_Iva.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Iva.png", + "Sprite/Portrait_Small_Smoke_Iva.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Iva.png", + "Sprite/Portrait_Small_Smoke_Iva.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_JadeVampireHunter.png": { + "assetName": "Portrait_Small_Smoke_JadeVampireHunter.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_JadeVampireHunter.png", + "Sprite/Portrait_Small_Smoke_JadeVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Jade_VBlood", + "candidateGuid": -1968372384, + "displayNameEn": "Jade the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_JadeVampireHunter.png", + "Sprite/Portrait_Small_Smoke_JadeVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_KeelyFrostArcher.png": { + "assetName": "Portrait_Small_Smoke_KeelyFrostArcher.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_KeelyFrostArcher.png", + "Sprite/Portrait_Small_Smoke_KeelyFrostArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Frostarrow_VBlood", + "candidateGuid": 1124739990, + "displayNameEn": "Keely the Frost Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_KeelyFrostArcher.png", + "Sprite/Portrait_Small_Smoke_KeelyFrostArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_LeandraShadowPriestess.png": { + "assetName": "Portrait_Small_Smoke_LeandraShadowPriestess.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_LeandraShadowPriestess.png", + "Sprite/Portrait_Small_Smoke_LeandraShadowPriestess.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_BishopOfShadows_VBlood", + "candidateGuid": 939467639, + "displayNameEn": "Leandra the Shadow Priestess", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_LeandraShadowPriestess.png", + "Sprite/Portrait_Small_Smoke_LeandraShadowPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_LidiaChaosArcher.png": { + "assetName": "Portrait_Small_Smoke_LidiaChaosArcher.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_LidiaChaosArcher.png", + "Sprite/Portrait_Small_Smoke_LidiaChaosArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Chaosarrow_VBlood", + "candidateGuid": 763273073, + "displayNameEn": "Lidia the Chaos Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_LidiaChaosArcher.png", + "Sprite/Portrait_Small_Smoke_LidiaChaosArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Livith.png": { + "assetName": "Portrait_Small_Smoke_Livith.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Livith.png", + "Sprite/Portrait_Small_Smoke_Livith.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Livith.png", + "Sprite/Portrait_Small_Smoke_Livith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_Lucie.png": { + "assetName": "Portrait_Small_Smoke_Lucie.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Lucie.png", + "Sprite/Portrait_Small_Smoke_Lucie.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Lucie.png", + "Sprite/Portrait_Small_Smoke_Lucie.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_MairwynElementalist.png": { + "assetName": "Portrait_Small_Smoke_MairwynElementalist.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_MairwynElementalist.png", + "Sprite/Portrait_Small_Smoke_MairwynElementalist.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ArchMage_VBlood", + "candidateGuid": -2013903325, + "displayNameEn": "Mairwyn the Elementalist", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_MairwynElementalist.png", + "Sprite/Portrait_Small_Smoke_MairwynElementalist.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_MatkaCurseWeaver.png": { + "assetName": "Portrait_Small_Smoke_MatkaCurseWeaver.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_MatkaCurseWeaver.png", + "Sprite/Portrait_Small_Smoke_MatkaCurseWeaver.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Cursed_Witch_VBlood", + "candidateGuid": -910296704, + "displayNameEn": "Matka the Curse Weaver", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_MatkaCurseWeaver.png", + "Sprite/Portrait_Small_Smoke_MatkaCurseWeaver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_MeredithBrightArcher.png": { + "assetName": "Portrait_Small_Smoke_MeredithBrightArcher.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_MeredithBrightArcher.png", + "Sprite/Portrait_Small_Smoke_MeredithBrightArcher.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Longbowman_LightArrow_Vblood", + "candidateGuid": 850622034, + "displayNameEn": "Meredith the Bright Archer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_MeredithBrightArcher.png", + "Sprite/Portrait_Small_Smoke_MeredithBrightArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Monster2.png": { + "assetName": "Portrait_Small_Smoke_Monster2.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Monster2.png", + "Sprite/Portrait_Small_Smoke_Monster2.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Monster2.png", + "Sprite/Portrait_Small_Smoke_Monster2.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_Morgana.png": { + "assetName": "Portrait_Small_Smoke_Morgana.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Morgana.png", + "Sprite/Portrait_Small_Smoke_Morgana.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Morgana.png", + "Sprite/Portrait_Small_Smoke_Morgana.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_MorianStormwingMatriarch.png": { + "assetName": "Portrait_Small_Smoke_MorianStormwingMatriarch.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_MorianStormwingMatriarch.png", + "Sprite/Portrait_Small_Smoke_MorianStormwingMatriarch.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Harpy_Matriarch_VBlood", + "candidateGuid": 685266977, + "displayNameEn": "Morian the Stormwing Matriarch", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_MorianStormwingMatriarch.png", + "Sprite/Portrait_Small_Smoke_MorianStormwingMatriarch.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_NicholausFallen.png": { + "assetName": "Portrait_Small_Smoke_NicholausFallen.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_NicholausFallen.png", + "Sprite/Portrait_Small_Smoke_NicholausFallen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Priest_VBlood", + "candidateGuid": 153390636, + "displayNameEn": "Nicholaus the Fallen", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_NicholausFallen.png", + "Sprite/Portrait_Small_Smoke_NicholausFallen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_NightmarshalStyxSunderer.png": { + "assetName": "Portrait_Small_Smoke_NightmarshalStyxSunderer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Small_Smoke_NightmarshalStyxSunderer.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_NightmarshalStyxSunderer.png", + "Sprite/Portrait_Small_Smoke_NightmarshalStyxSunderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_OctavianMilitiaCaptain.png": { + "assetName": "Portrait_Small_Smoke_OctavianMilitiaCaptain.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Small_Smoke_OctavianMilitiaCaptain.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Leader_VBlood", + "candidateGuid": 1688478381, + "displayNameEn": "Octavian the Militia Captain", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_OctavianMilitiaCaptain.png", + "Sprite/Portrait_Small_Smoke_OctavianMilitiaCaptain.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Overseer.png": { + "assetName": "Portrait_Small_Smoke_Overseer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Overseer.png", + "Sprite/Portrait_Small_Smoke_Overseer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Overseer_VBlood", + "candidateGuid": -26105228, + "displayNameEn": "Sir Magnus the Overseer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Overseer.png", + "Sprite/Portrait_Small_Smoke_Overseer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_PMK01.png": { + "assetName": "Portrait_Small_Smoke_PMK01.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_PMK01.png", + "Sprite/Portrait_Small_Smoke_PMK01.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_PMK01.png", + "Sprite/Portrait_Small_Smoke_PMK01.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_PoloraFeywalker.png": { + "assetName": "Portrait_Small_Smoke_PoloraFeywalker.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_PoloraFeywalker.png", + "Sprite/Portrait_Small_Smoke_PoloraFeywalker.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Poloma_VBlood", + "candidateGuid": -484556888, + "displayNameEn": "Polora the Feywalker", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_PoloraFeywalker.png", + "Sprite/Portrait_Small_Smoke_PoloraFeywalker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Professor.png": { + "assetName": "Portrait_Small_Smoke_Professor.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Professor.png", + "Sprite/Portrait_Small_Smoke_Professor.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_TheProfessor_VBlood", + "candidateGuid": 814083983, + "displayNameEn": "Henry Blackbrew the Doctor", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Professor.png", + "Sprite/Portrait_Small_Smoke_Professor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Purifier.png": { + "assetName": "Portrait_Small_Smoke_Purifier.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Purifier.png", + "Sprite/Portrait_Small_Smoke_Purifier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Gloomrot_Purifier_VBlood", + "candidateGuid": 106480588, + "displayNameEn": "Angram the Purifier", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Purifier.png", + "Sprite/Portrait_Small_Smoke_Purifier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_PutridRat.png": { + "assetName": "Portrait_Small_Smoke_PutridRat.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_PutridRat.png", + "Sprite/Portrait_Small_Smoke_PutridRat.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Vermin_DireRat_VBlood", + "candidateGuid": -2039908510, + "displayNameEn": "Nibbles the Putrid Rat", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_PutridRat.png", + "Sprite/Portrait_Small_Smoke_PutridRat.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_QuinceyBanditKing.png": { + "assetName": "Portrait_Small_Smoke_QuinceyBanditKing.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_QuinceyBanditKing.png", + "Sprite/Portrait_Small_Smoke_QuinceyBanditKing.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Tourok_VBlood", + "candidateGuid": -1659822956, + "displayNameEn": "Quincey the Bandit King", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_QuinceyBanditKing.png", + "Sprite/Portrait_Small_Smoke_QuinceyBanditKing.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_RazielShepherd.png": { + "assetName": "Portrait_Small_Smoke_RazielShepherd.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_RazielShepherd.png", + "Sprite/Portrait_Small_Smoke_RazielShepherd.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_BishopOfDunley_VBlood", + "candidateGuid": -680831417, + "displayNameEn": "Raziel the Shepherd", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_RazielShepherd.png", + "Sprite/Portrait_Small_Smoke_RazielShepherd.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_RufusForeman.png": { + "assetName": "Portrait_Small_Smoke_RufusForeman.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_RufusForeman.png", + "Sprite/Portrait_Small_Smoke_RufusForeman.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Bandit_Foreman_VBlood", + "candidateGuid": 2122229952, + "displayNameEn": "Rufus the Foreman", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_RufusForeman.png", + "Sprite/Portrait_Small_Smoke_RufusForeman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Scholar.png": { + "assetName": "Portrait_Small_Smoke_Scholar.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Scholar.png", + "Sprite/Portrait_Small_Smoke_Scholar.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Scholar.png", + "Sprite/Portrait_Small_Smoke_Scholar.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_SergeantRailgunner.png": { + "assetName": "Portrait_Small_Smoke_SergeantRailgunner.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_SergeantRailgunner.png", + "Sprite/Portrait_Small_Smoke_SergeantRailgunner.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_SergeantRailgunner.png", + "Sprite/Portrait_Small_Smoke_SergeantRailgunner.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_ShadowInfiltrator.png": { + "assetName": "Portrait_Small_Smoke_ShadowInfiltrator.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_ShadowInfiltrator.png", + "Sprite/Portrait_Small_Smoke_ShadowInfiltrator.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_ShadowInfiltrator.png", + "Sprite/Portrait_Small_Smoke_ShadowInfiltrator.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_SolarusImmaculate.png": { + "assetName": "Portrait_Small_Smoke_SolarusImmaculate.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_SolarusImmaculate.png", + "Sprite/Portrait_Small_Smoke_SolarusImmaculate.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Paladin_VBlood", + "candidateGuid": -740796338, + "displayNameEn": "Solarus the Immaculate", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_SolarusImmaculate.png", + "Sprite/Portrait_Small_Smoke_SolarusImmaculate.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Sommelier.png": { + "assetName": "Portrait_Small_Smoke_Sommelier.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Sommelier.png", + "Sprite/Portrait_Small_Smoke_Sommelier.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_ChurchOfLight_Sommelier_VBlood", + "candidateGuid": 192051202, + "displayNameEn": "Baron du Bouchon the Sommelier", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Sommelier.png", + "Sprite/Portrait_Small_Smoke_Sommelier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_TerahGeomancer.png": { + "assetName": "Portrait_Small_Smoke_TerahGeomancer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_TerahGeomancer.png", + "Sprite/Portrait_Small_Smoke_TerahGeomancer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Geomancer_Human_VBlood", + "candidateGuid": -1065970933, + "displayNameEn": "Terah the Geomancer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_TerahGeomancer.png", + "Sprite/Portrait_Small_Smoke_TerahGeomancer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_TerrorclawOgre.png": { + "assetName": "Portrait_Small_Smoke_TerrorclawOgre.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_TerrorclawOgre.png", + "Sprite/Portrait_Small_Smoke_TerrorclawOgre.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Winter_Yeti_VBlood", + "candidateGuid": -1347412392, + "displayNameEn": "Terrorclaw the Ogre", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_TerrorclawOgre.png", + "Sprite/Portrait_Small_Smoke_TerrorclawOgre.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_TristanVampireHunter.png": { + "assetName": "Portrait_Small_Smoke_TristanVampireHunter.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_TristanVampireHunter.png", + "Sprite/Portrait_Small_Smoke_TristanVampireHunter.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_VHunter_Leader_VBlood", + "candidateGuid": -1449631170, + "displayNameEn": "Tristan the Vampire Hunter", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_TristanVampireHunter.png", + "Sprite/Portrait_Small_Smoke_TristanVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_UndeadGeneral.png": { + "assetName": "Portrait_Small_Smoke_UndeadGeneral.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_UndeadGeneral.png", + "Sprite/Portrait_Small_Smoke_UndeadGeneral.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Undead_Leader_Vblood", + "candidateGuid": -1365931036, + "displayNameEn": "Kriig the Undead General", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_UndeadGeneral.png", + "Sprite/Portrait_Small_Smoke_UndeadGeneral.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_UngoraSpiderQueen.png": { + "assetName": "Portrait_Small_Smoke_UngoraSpiderQueen.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_UngoraSpiderQueen.png", + "Sprite/Portrait_Small_Smoke_UngoraSpiderQueen.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Spider_Queen_VBlood", + "candidateGuid": -548489519, + "displayNameEn": "Ungora the Spider Queen", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_UngoraSpiderQueen.png", + "Sprite/Portrait_Small_Smoke_UngoraSpiderQueen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_UnholyCommander.png": { + "assetName": "Portrait_Small_Smoke_UnholyCommander.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_UnholyCommander.png", + "Sprite/Portrait_Small_Smoke_UnholyCommander.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_UnholyCommander.png", + "Sprite/Portrait_Small_Smoke_UnholyCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_Unknown.png": { + "assetName": "Portrait_Small_Smoke_Unknown.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Unknown.png", + "Sprite/Portrait_Small_Smoke_Unknown.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Unknown.png", + "Sprite/Portrait_Small_Smoke_Unknown.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_VincentFrostbringer.png": { + "assetName": "Portrait_Small_Smoke_VincentFrostbringer.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_VincentFrostbringer.png", + "Sprite/Portrait_Small_Smoke_VincentFrostbringer.png" + ], + "joinStatus": "circumstantial", + "approvalStatus": "pending", + "candidatePrefab": "CHAR_Militia_Guard_VBlood", + "candidateGuid": -29797003, + "displayNameEn": "Vincent the Frostbringer", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_VincentFrostbringer.png", + "Sprite/Portrait_Small_Smoke_VincentFrostbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ], + "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + }, + "Portrait_Small_Smoke_Voltage.png": { + "assetName": "Portrait_Small_Smoke_Voltage.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_Voltage.png", + "Sprite/Portrait_Small_Smoke_Voltage.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_Voltage.png", + "Sprite/Portrait_Small_Smoke_Voltage.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_WillfredWerewolfChief.png": { + "assetName": "Portrait_Small_Smoke_WillfredWerewolfChief.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_WillfredWerewolfChief.png", + "Sprite/Portrait_Small_Smoke_WillfredWerewolfChief.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_WillfredWerewolfChief.png", + "Sprite/Portrait_Small_Smoke_WillfredWerewolfChief.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + }, + "Portrait_Small_Smoke_WingedHorror2.png": { + "assetName": "Portrait_Small_Smoke_WingedHorror2.png", + "assetFamily": "portrait-small-smoke", + "assetSourceRefs": [ + "Texture2D/Portrait_Small_Smoke_WingedHorror2.png", + "Sprite/Portrait_Small_Smoke_WingedHorror2.png" + ], + "joinStatus": "unsafe", + "approvalStatus": "rejected", + "evidenceRefs": [ + "Texture2D/Portrait_Small_Smoke_WingedHorror2.png", + "Sprite/Portrait_Small_Smoke_WingedHorror2.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json" + ], + "reason": "portrait alias did not match a current V Blood NPC row" + } + } +} diff --git a/data/enrichment/npc-portrait-map.json b/data/enrichment/npc-portrait-map.json new file mode 100644 index 0000000000..57c0f87758 --- /dev/null +++ b/data/enrichment/npc-portrait-map.json @@ -0,0 +1,177 @@ +{ + "schemaVersion": 1, + "sourceKind": "provisional-vblood-portrait-map", + "sourceRef": "data/enrichment/npc-portrait-candidates.json", + "totalCurrentVbloodRows": 69, + "entriesByPrefab": { + "CHAR_ArchMage_VBlood": { + "prefab": "CHAR_ArchMage_VBlood", + "guid": -2013903325, + "displayNameEn": "Mairwyn the Elementalist", + "portraitAssetName": "CHAR_ArchMage_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_ArchMage_VBlood_HeadPortrait.png", + "Sprite/CHAR_ArchMage_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Bandit_Bomber_VBlood": { + "prefab": "CHAR_Bandit_Bomber_VBlood", + "guid": 1896428751, + "displayNameEn": "Clive the Firestarter", + "portraitAssetName": "CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Bomber_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Bandit_Foreman_VBlood": { + "prefab": "CHAR_Bandit_Foreman_VBlood", + "guid": 2122229952, + "displayNameEn": "Rufus the Foreman", + "portraitAssetName": "CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Foreman_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Bandit_Stalker_VBlood": { + "prefab": "CHAR_Bandit_Stalker_VBlood", + "guid": 1106149033, + "displayNameEn": "Grayson the Armourer", + "portraitAssetName": "CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Stalker_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Bandit_Tourok_VBlood": { + "prefab": "CHAR_Bandit_Tourok_VBlood", + "guid": -1659822956, + "displayNameEn": "Quincey the Bandit King", + "portraitAssetName": "CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "Sprite/CHAR_Bandit_Tourok_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_BatVampire_VBlood": { + "prefab": "CHAR_BatVampire_VBlood", + "guid": 1112948824, + "displayNameEn": "Lord Styx the Night Champion", + "portraitAssetName": "CHAR_BatVampire_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_BatVampire_VBlood_HeadPortrait.png", + "Sprite/CHAR_BatVampire_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Militia_Guard_VBlood": { + "prefab": "CHAR_Militia_Guard_VBlood", + "guid": -29797003, + "displayNameEn": "Vincent the Frostbringer", + "portraitAssetName": "CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "Sprite/CHAR_Militia_Guard_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Militia_Leader_VBlood": { + "prefab": "CHAR_Militia_Leader_VBlood", + "guid": 1688478381, + "displayNameEn": "Octavian the Militia Captain", + "portraitAssetName": "CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "Sprite/CHAR_Militia_Leader_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + }, + "CHAR_Vampire_BloodKnight_VBlood": { + "prefab": "CHAR_Vampire_BloodKnight_VBlood", + "guid": 495971434, + "displayNameEn": "General Valencia the Depraved", + "portraitAssetName": "Portrait_Large_Normal_BloodCommander.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: BloodCommander is General Valencia the Depraved.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_BloodCommander.png", + "Sprite/Portrait_Large_Normal_BloodCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Vampire_IceRanger_VBlood": { + "prefab": "CHAR_Vampire_IceRanger_VBlood", + "guid": 795262842, + "displayNameEn": "General Elena the Hollow", + "portraitAssetName": "Portrait_Large_Normal_FrostCommander.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: FrostCommander is General Elena the Hollow.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FrostCommander.png", + "Sprite/Portrait_Large_Normal_FrostCommander.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Vermin_DireRat_VBlood": { + "prefab": "CHAR_Vermin_DireRat_VBlood", + "guid": -2039908510, + "displayNameEn": "Nibbles the Putrid Rat", + "portraitAssetName": "CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "portraitAssetFamily": "char-vblood-headportrait", + "joinStatus": "source-backed", + "evidenceRefs": [ + "Texture2D/CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "Sprite/CHAR_Vermin_DireRat_VBlood_HeadPortrait.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/All.json" + ] + } + } +} From ccb7407b07997ccece0e5b462a2867b6640dbf48 Mon Sep 17 00:00:00 2001 From: mfoltz Date: Wed, 6 May 2026 16:13:48 -0500 Subject: [PATCH 4/6] Align npc portrait coverage and shared unsafe prefab pattern --- data/enrichment/enrichment-coverage.json | 2 +- scripts/npc-portraits.ts | 4 ++-- scripts/refresh-db-assets.ts | 7 ++++++- scripts/validate-generated-data.ts | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/data/enrichment/enrichment-coverage.json b/data/enrichment/enrichment-coverage.json index a2a69ced8c..deb8aa0f1c 100644 --- a/data/enrichment/enrichment-coverage.json +++ b/data/enrichment/enrichment-coverage.json @@ -67,7 +67,7 @@ "matched": 11, "coveragePct": 0.1594, "signal": "high-signal", - "lowSignalExcluded": 182 + "lowSignalExcluded": 47 }, "quest-display-map": { "total": 163, diff --git a/scripts/npc-portraits.ts b/scripts/npc-portraits.ts index 8753f77451..94180796b5 100644 --- a/scripts/npc-portraits.ts +++ b/scripts/npc-portraits.ts @@ -84,7 +84,7 @@ const manualAttestedPortraits: Record): NpcPortraitCandidateEntry { diff --git a/scripts/refresh-db-assets.ts b/scripts/refresh-db-assets.ts index 7133a8da5a..48115ac6b6 100644 --- a/scripts/refresh-db-assets.ts +++ b/scripts/refresh-db-assets.ts @@ -3494,6 +3494,11 @@ async function main() { (entry.level !== undefined || Boolean(entry.bloodType) || Boolean(entry.faction) || Boolean(entry.unitCategory) || entry.isVBlood === true || entry.isServant === true) && isLowSignalSource(entry.sourceKind) ).length; + const npcPortraitLowSignalPrefabs = new Set( + Object.values(stableNpcPortraitSnapshots.candidates.entriesByAssetName).flatMap((entry) => + entry.joinStatus === "circumstantial" && entry.candidatePrefab ? [entry.candidatePrefab] : [] + ) + ).size; const coverage: Record = { "ability-tooltip-map": toCoverage(stableCatalogSnapshot.entries.length, abilityTooltipMatched, abilityTooltipLowSignal), @@ -3504,7 +3509,7 @@ async function main() { "npc-portrait-map": toCoverage( stableNpcPortraitSnapshots.portraitMap.totalCurrentVbloodRows, Object.keys(stableNpcPortraitSnapshots.portraitMap.entriesByPrefab).length, - Object.values(stableNpcPortraitSnapshots.candidates.entriesByAssetName).filter((entry) => entry.joinStatus === "circumstantial").length + npcPortraitLowSignalPrefabs ), "recipe-link-map": toCoverage(Object.keys(stableRecipeLinkSnapshot).length, recipeLinkMatched, recipeLinkLowSignal) }; diff --git a/scripts/validate-generated-data.ts b/scripts/validate-generated-data.ts index ea1754515f..1b098b3d79 100644 --- a/scripts/validate-generated-data.ts +++ b/scripts/validate-generated-data.ts @@ -2,7 +2,7 @@ import { access, readFile, readdir } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { bloodHuntsSourceKind, nameKeyToLocalizationGuid } from "./blood-hunts"; -import { npcPortraitCandidatesSourceKind, npcPortraitMapSourceKind } from "./npc-portraits"; +import { npcPortraitCandidatesSourceKind, npcPortraitMapSourceKind, unsafeNpcPortraitPrefabPattern } from "./npc-portraits"; import { isSafeSlug } from "../src/lib/slug"; import { extractTextVariables, @@ -413,7 +413,7 @@ async function validateNpcPortraitMaps(repoRoot: string): Promise { assert(entry.prefab === prefab, `${source}: prefab must match map key`); assert(entry.joinStatus === "source-backed" || entry.joinStatus === "user-attested", `${source}: unusable joinStatus '${entry.joinStatus}' promoted into portrait map`); assert(entry.joinStatus !== "user-attested" || entry.approvalStatus === "approved", `${source}: user-attested row must be approved before promotion`); - assert(!/(?:GateBoss|Primal|Minion|Tail|ShadowClone|_UNUSED)/i.test(prefab), `${source}: unsafe prefab variant promoted into portrait map`); + assert(!unsafeNpcPortraitPrefabPattern.test(prefab), `${source}: unsafe prefab variant promoted into portrait map`); assert(allPrefabs[prefab] === entry.guid, `${source}: prefab '${prefab}' does not join through ${allPrefabsPath}`); assert(npcDisplay[prefab]?.displayNameEn === entry.displayNameEn, `${source}: displayNameEn does not match ${npcDisplayPath}`); const classificationEntry = npcClassification[prefab]; From 046361155f023126ccc7f4a32057e27ab0b1fa8a Mon Sep 17 00:00:00 2001 From: mfoltz Date: Wed, 6 May 2026 18:02:17 -0500 Subject: [PATCH 5/6] Approve NPC portrait candidates --- data/enrichment/enrichment-coverage.json | 4 +- data/enrichment/npc-portrait-candidates.json | 273 ++++---- data/enrichment/npc-portrait-map.json | 663 +++++++++++++++++++ scripts/npc-portraits.test.ts | 27 +- scripts/npc-portraits.ts | 184 ++++- 5 files changed, 1024 insertions(+), 127 deletions(-) diff --git a/data/enrichment/enrichment-coverage.json b/data/enrichment/enrichment-coverage.json index deb8aa0f1c..ac298b9777 100644 --- a/data/enrichment/enrichment-coverage.json +++ b/data/enrichment/enrichment-coverage.json @@ -64,8 +64,8 @@ }, "npc-portrait-map": { "total": 69, - "matched": 11, - "coveragePct": 0.1594, + "matched": 50, + "coveragePct": 0.7246, "signal": "high-signal", "lowSignalExcluded": 47 }, diff --git a/data/enrichment/npc-portrait-candidates.json b/data/enrichment/npc-portrait-candidates.json index 603188c2f0..07e214f1d3 100644 --- a/data/enrichment/npc-portrait-candidates.json +++ b/data/enrichment/npc-portrait-candidates.json @@ -215,8 +215,9 @@ "Texture2D/Portrait_Large_Normal_AlphaWolf.png", "Sprite/Portrait_Large_Normal_AlphaWolf.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_AlphaWolf.png is Alpha the White Wolf.", "candidatePrefab": "CHAR_Forest_Wolf_VBlood", "candidateGuid": -1905691330, "displayNameEn": "Alpha the White Wolf", @@ -227,7 +228,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_ArenaChampion.png": { "assetName": "Portrait_Large_Normal_ArenaChampion.png", @@ -236,8 +237,9 @@ "Texture2D/Portrait_Large_Normal_ArenaChampion.png", "Sprite/Portrait_Large_Normal_ArenaChampion.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ArenaChampion.png is Gaius the Cursed Champion.", "candidatePrefab": "CHAR_Undead_ArenaChampion_VBlood", "candidateGuid": -753453016, "displayNameEn": "Gaius the Cursed Champion", @@ -248,7 +250,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_AzarielSunbringer.png": { "assetName": "Portrait_Large_Normal_AzarielSunbringer.png", @@ -257,8 +259,9 @@ "Texture2D/Portrait_Large_Normal_AzarielSunbringer.png", "Sprite/Portrait_Large_Normal_AzarielSunbringer.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_AzarielSunbringer.png is Azariel the Sunbringer.", "candidatePrefab": "CHAR_ChurchOfLight_Cardinal_VBlood", "candidateGuid": 114912615, "displayNameEn": "Azariel the Sunbringer", @@ -269,7 +272,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_BeatriceTailor.png": { "assetName": "Portrait_Large_Normal_BeatriceTailor.png", @@ -278,8 +281,9 @@ "Texture2D/Portrait_Large_Normal_BeatriceTailor.png", "Sprite/Portrait_Large_Normal_BeatriceTailor.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_BeatriceTailor.png is Beatrice the Tailor.", "candidatePrefab": "CHAR_Villager_Tailor_VBlood", "candidateGuid": -1942352521, "displayNameEn": "Beatrice the Tailor", @@ -290,7 +294,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_BloodCommander.png": { "assetName": "Portrait_Large_Normal_BloodCommander.png", @@ -321,8 +325,9 @@ "Texture2D/Portrait_Large_Normal_ChristinaSunPriestess.png", "Sprite/Portrait_Large_Normal_ChristinaSunPriestess.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ChristinaSunPriestess.png is Christina the Sun Priestess.", "candidatePrefab": "CHAR_Militia_Nun_VBlood", "candidateGuid": -99012450, "displayNameEn": "Christina the Sun Priestess", @@ -333,7 +338,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_CliveFirestarter.png": { "assetName": "Portrait_Large_Normal_CliveFirestarter.png", @@ -363,8 +368,9 @@ "Texture2D/Portrait_Large_Normal_CursedSmith.png", "Sprite/Portrait_Large_Normal_CursedSmith.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_CursedSmith.png is Cyril the Cursed Smith.", "candidatePrefab": "CHAR_Undead_CursedSmith_VBlood", "candidateGuid": 326378955, "displayNameEn": "Cyril the Cursed Smith", @@ -375,7 +381,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_CursedWanderer.png": { "assetName": "Portrait_Large_Normal_CursedWanderer.png", @@ -384,8 +390,9 @@ "Texture2D/Portrait_Large_Normal_CursedWanderer.png", "Sprite/Portrait_Large_Normal_CursedWanderer.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_CursedWanderer.png is Ben the Old Wanderer.", "candidatePrefab": "CHAR_Villager_CursedWanderer_VBlood", "candidateGuid": 109969450, "displayNameEn": "Ben the Old Wanderer", @@ -396,7 +403,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Dracula.png": { "assetName": "Portrait_Large_Normal_Dracula.png", @@ -405,8 +412,9 @@ "Texture2D/Portrait_Large_Normal_Dracula.png", "Sprite/Portrait_Large_Normal_Dracula.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Dracula.png is Dracula the Immortal King.", "candidatePrefab": "CHAR_Vampire_Dracula_VBlood", "candidateGuid": -327335305, "displayNameEn": "Dracula the Immortal King", @@ -417,7 +425,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_DukeBalaton.png": { "assetName": "Portrait_Large_Normal_DukeBalaton.png", @@ -426,8 +434,9 @@ "Texture2D/Portrait_Large_Normal_DukeBalaton.png", "Sprite/Portrait_Large_Normal_DukeBalaton.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_DukeBalaton.png is Albert the Duke of Balaton.", "candidatePrefab": "CHAR_Cursed_ToadKing_VBlood", "candidateGuid": -203043163, "displayNameEn": "Albert the Duke of Balaton", @@ -438,7 +447,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_ErrolStonebreaker.png": { "assetName": "Portrait_Large_Normal_ErrolStonebreaker.png", @@ -447,8 +456,9 @@ "Texture2D/Portrait_Large_Normal_ErrolStonebreaker.png", "Sprite/Portrait_Large_Normal_ErrolStonebreaker.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ErrolStonebreaker.png is Errol the Stonebreaker.", "candidatePrefab": "CHAR_Bandit_StoneBreaker_VBlood", "candidateGuid": -2025101517, "displayNameEn": "Errol the Stonebreaker", @@ -459,7 +469,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Fabian.png": { "assetName": "Portrait_Large_Normal_Fabian.png", @@ -485,8 +495,9 @@ "Texture2D/Portrait_Large_Normal_FerociousBear.png", "Sprite/Portrait_Large_Normal_FerociousBear.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_FerociousBear.png is Kodia the Ferocious Bear.", "candidatePrefab": "CHAR_Forest_Bear_Dire_Vblood", "candidateGuid": -1391546313, "displayNameEn": "Kodia the Ferocious Bear", @@ -497,7 +508,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Fisherman.png": { "assetName": "Portrait_Large_Normal_Fisherman.png", @@ -506,8 +517,9 @@ "Texture2D/Portrait_Large_Normal_Fisherman.png", "Sprite/Portrait_Large_Normal_Fisherman.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Fisherman.png is Finn the Fisherman.", "candidatePrefab": "CHAR_Bandit_Fisherman_VBlood", "candidateGuid": -2122682556, "displayNameEn": "Finn the Fisherman", @@ -518,7 +530,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_ForgeBinder.png": { "assetName": "Portrait_Large_Normal_ForgeBinder.png", @@ -527,8 +539,9 @@ "Texture2D/Portrait_Large_Normal_ForgeBinder.png", "Sprite/Portrait_Large_Normal_ForgeBinder.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ForgeBinder.png is Dantos the Forgebinder.", "candidatePrefab": "CHAR_Blackfang_Valyr_VBlood", "candidateGuid": 173259239, "displayNameEn": "Dantos the Forgebinder", @@ -539,7 +552,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_FoulrotSoultaker.png": { "assetName": "Portrait_Large_Normal_FoulrotSoultaker.png", @@ -548,8 +561,9 @@ "Texture2D/Portrait_Large_Normal_FoulrotSoultaker.png", "Sprite/Portrait_Large_Normal_FoulrotSoultaker.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_FoulrotSoultaker.png is Foulrot the Soultaker.", "candidatePrefab": "CHAR_Undead_ZealousCultist_VBlood", "candidateGuid": -1208888966, "displayNameEn": "Foulrot the Soultaker", @@ -560,7 +574,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_FrostCommander.png": { "assetName": "Portrait_Large_Normal_FrostCommander.png", @@ -591,8 +605,9 @@ "Texture2D/Portrait_Large_Normal_FrostmawMountainTerror.png", "Sprite/Portrait_Large_Normal_FrostmawMountainTerror.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_FrostmawMountainTerror.png is Frostmaw the Mountain Terror.", "candidatePrefab": "CHAR_Wendigo_VBlood", "candidateGuid": 24378719, "displayNameEn": "Frostmaw the Mountain Terror", @@ -603,7 +618,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_GerardCarver.png": { "assetName": "Portrait_Large_Normal_GerardCarver.png", @@ -629,8 +644,9 @@ "Texture2D/Portrait_Large_Normal_Glassblower.png", "Sprite/Portrait_Large_Normal_Glassblower.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Glassblower.png is Grethel the Glassblower.", "candidatePrefab": "CHAR_Militia_Glassblower_VBlood", "candidateGuid": 910988233, "displayNameEn": "Grethel the Glassblower", @@ -641,7 +657,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_GorecrusherBehemoth.png": { "assetName": "Portrait_Large_Normal_GorecrusherBehemoth.png", @@ -650,8 +666,9 @@ "Texture2D/Portrait_Large_Normal_GorecrusherBehemoth.png", "Sprite/Portrait_Large_Normal_GorecrusherBehemoth.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_GorecrusherBehemoth.png is Gorecrusher the Behemoth.", "candidatePrefab": "CHAR_Cursed_MountainBeast_VBlood", "candidateGuid": -1936575244, "displayNameEn": "Gorecrusher the Behemoth", @@ -662,7 +679,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_GoreswineRavager.png": { "assetName": "Portrait_Large_Normal_GoreswineRavager.png", @@ -671,8 +688,9 @@ "Texture2D/Portrait_Large_Normal_GoreswineRavager.png", "Sprite/Portrait_Large_Normal_GoreswineRavager.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_GoreswineRavager.png is Goreswine the Ravager.", "candidatePrefab": "CHAR_Undead_BishopOfDeath_VBlood", "candidateGuid": 577478542, "displayNameEn": "Goreswine the Ravager", @@ -683,7 +701,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_GraysonArmourer.png": { "assetName": "Portrait_Large_Normal_GraysonArmourer.png", @@ -730,8 +748,9 @@ "Texture2D/Portrait_Large_Normal_JadeVampireHunter.png", "Sprite/Portrait_Large_Normal_JadeVampireHunter.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_JadeVampireHunter.png is Jade the Vampire Hunter.", "candidatePrefab": "CHAR_VHunter_Jade_VBlood", "candidateGuid": -1968372384, "displayNameEn": "Jade the Vampire Hunter", @@ -742,7 +761,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_KeelyFrostArcher.png": { "assetName": "Portrait_Large_Normal_KeelyFrostArcher.png", @@ -751,8 +770,9 @@ "Texture2D/Portrait_Large_Normal_KeelyFrostArcher.png", "Sprite/Portrait_Large_Normal_KeelyFrostArcher.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_KeelyFrostArcher.png is Keely the Frost Archer.", "candidatePrefab": "CHAR_Bandit_Frostarrow_VBlood", "candidateGuid": 1124739990, "displayNameEn": "Keely the Frost Archer", @@ -763,7 +783,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_LeandraShadowPriestess.png": { "assetName": "Portrait_Large_Normal_LeandraShadowPriestess.png", @@ -772,8 +792,9 @@ "Texture2D/Portrait_Large_Normal_LeandraShadowPriestess.png", "Sprite/Portrait_Large_Normal_LeandraShadowPriestess.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_LeandraShadowPriestess.png is Leandra the Shadow Priestess.", "candidatePrefab": "CHAR_Undead_BishopOfShadows_VBlood", "candidateGuid": 939467639, "displayNameEn": "Leandra the Shadow Priestess", @@ -784,7 +805,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_LidiaChaosArcher.png": { "assetName": "Portrait_Large_Normal_LidiaChaosArcher.png", @@ -793,8 +814,9 @@ "Texture2D/Portrait_Large_Normal_LidiaChaosArcher.png", "Sprite/Portrait_Large_Normal_LidiaChaosArcher.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_LidiaChaosArcher.png is Lidia the Chaos Archer.", "candidatePrefab": "CHAR_Bandit_Chaosarrow_VBlood", "candidateGuid": 763273073, "displayNameEn": "Lidia the Chaos Archer", @@ -805,7 +827,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Livith.png": { "assetName": "Portrait_Large_Normal_Livith.png", @@ -869,8 +891,9 @@ "Texture2D/Portrait_Large_Normal_MatkaCurseWeaver.png", "Sprite/Portrait_Large_Normal_MatkaCurseWeaver.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_MatkaCurseWeaver.png is Matka the Curse Weaver.", "candidatePrefab": "CHAR_Cursed_Witch_VBlood", "candidateGuid": -910296704, "displayNameEn": "Matka the Curse Weaver", @@ -881,7 +904,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_MeredithBrightArcher.png": { "assetName": "Portrait_Large_Normal_MeredithBrightArcher.png", @@ -890,8 +913,9 @@ "Texture2D/Portrait_Large_Normal_MeredithBrightArcher.png", "Sprite/Portrait_Large_Normal_MeredithBrightArcher.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_MeredithBrightArcher.png is Meredith the Bright Archer.", "candidatePrefab": "CHAR_Militia_Longbowman_LightArrow_Vblood", "candidateGuid": 850622034, "displayNameEn": "Meredith the Bright Archer", @@ -902,7 +926,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Monster.png": { "assetName": "Portrait_Large_Normal_Monster.png", @@ -945,8 +969,9 @@ "Texture2D/Portrait_Large_Normal_MorianStormwingMatriarch.png", "Sprite/Portrait_Large_Normal_MorianStormwingMatriarch.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_MorianStormwingMatriarch.png is Morian the Stormwing Matriarch.", "candidatePrefab": "CHAR_Harpy_Matriarch_VBlood", "candidateGuid": 685266977, "displayNameEn": "Morian the Stormwing Matriarch", @@ -957,7 +982,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_NicholausFallen.png": { "assetName": "Portrait_Large_Normal_NicholausFallen.png", @@ -966,8 +991,9 @@ "Texture2D/Portrait_Large_Normal_NicholausFallen.png", "Sprite/Portrait_Large_Normal_NicholausFallen.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_NicholausFallen.png is Nicholaus the Fallen.", "candidatePrefab": "CHAR_Undead_Priest_VBlood", "candidateGuid": 153390636, "displayNameEn": "Nicholaus the Fallen", @@ -978,7 +1004,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_NightmarshalStyxSunderer.png": { "assetName": "Portrait_Large_Normal_NightmarshalStyxSunderer.png", @@ -1025,8 +1051,9 @@ "Texture2D/Portrait_Large_Normal_Overseer.png", "Sprite/Portrait_Large_Normal_Overseer.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Overseer.png is Sir Magnus the Overseer.", "candidatePrefab": "CHAR_ChurchOfLight_Overseer_VBlood", "candidateGuid": -26105228, "displayNameEn": "Sir Magnus the Overseer", @@ -1037,7 +1064,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_PMK01.png": { "assetName": "Portrait_Large_Normal_PMK01.png", @@ -1063,8 +1090,9 @@ "Texture2D/Portrait_Large_Normal_PoloraFeywalker.png", "Sprite/Portrait_Large_Normal_PoloraFeywalker.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_PoloraFeywalker.png is Polora the Feywalker.", "candidatePrefab": "CHAR_Poloma_VBlood", "candidateGuid": -484556888, "displayNameEn": "Polora the Feywalker", @@ -1075,7 +1103,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Professor.png": { "assetName": "Portrait_Large_Normal_Professor.png", @@ -1084,8 +1112,9 @@ "Texture2D/Portrait_Large_Normal_Professor.png", "Sprite/Portrait_Large_Normal_Professor.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Professor.png is Henry Blackbrew the Doctor.", "candidatePrefab": "CHAR_Gloomrot_TheProfessor_VBlood", "candidateGuid": 814083983, "displayNameEn": "Henry Blackbrew the Doctor", @@ -1096,7 +1125,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Purifier.png": { "assetName": "Portrait_Large_Normal_Purifier.png", @@ -1105,8 +1134,9 @@ "Texture2D/Portrait_Large_Normal_Purifier.png", "Sprite/Portrait_Large_Normal_Purifier.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Purifier.png is Angram the Purifier.", "candidatePrefab": "CHAR_Gloomrot_Purifier_VBlood", "candidateGuid": 106480588, "displayNameEn": "Angram the Purifier", @@ -1117,7 +1147,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_PutridRat.png": { "assetName": "Portrait_Large_Normal_PutridRat.png", @@ -1168,8 +1198,9 @@ "Texture2D/Portrait_Large_Normal_RazielShepherd.png", "Sprite/Portrait_Large_Normal_RazielShepherd.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_RazielShepherd.png is Raziel the Shepherd.", "candidatePrefab": "CHAR_Militia_BishopOfDunley_VBlood", "candidateGuid": -680831417, "displayNameEn": "Raziel the Shepherd", @@ -1180,7 +1211,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_RufusForeman.png": { "assetName": "Portrait_Large_Normal_RufusForeman.png", @@ -1261,8 +1292,9 @@ "Texture2D/Portrait_Large_Normal_SolarusImmaculate.png", "Sprite/Portrait_Large_Normal_SolarusImmaculate.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_SolarusImmaculate.png is Solarus the Immaculate.", "candidatePrefab": "CHAR_ChurchOfLight_Paladin_VBlood", "candidateGuid": -740796338, "displayNameEn": "Solarus the Immaculate", @@ -1273,7 +1305,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_Sommelier.png": { "assetName": "Portrait_Large_Normal_Sommelier.png", @@ -1282,8 +1314,9 @@ "Texture2D/Portrait_Large_Normal_Sommelier.png", "Sprite/Portrait_Large_Normal_Sommelier.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Sommelier.png is Baron du Bouchon the Sommelier.", "candidatePrefab": "CHAR_ChurchOfLight_Sommelier_VBlood", "candidateGuid": 192051202, "displayNameEn": "Baron du Bouchon the Sommelier", @@ -1294,7 +1327,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_TerahGeomancer.png": { "assetName": "Portrait_Large_Normal_TerahGeomancer.png", @@ -1303,8 +1336,9 @@ "Texture2D/Portrait_Large_Normal_TerahGeomancer.png", "Sprite/Portrait_Large_Normal_TerahGeomancer.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_TerahGeomancer.png is Terah the Geomancer.", "candidatePrefab": "CHAR_Geomancer_Human_VBlood", "candidateGuid": -1065970933, "displayNameEn": "Terah the Geomancer", @@ -1315,7 +1349,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_TerrorclawOgre.png": { "assetName": "Portrait_Large_Normal_TerrorclawOgre.png", @@ -1324,8 +1358,9 @@ "Texture2D/Portrait_Large_Normal_TerrorclawOgre.png", "Sprite/Portrait_Large_Normal_TerrorclawOgre.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_TerrorclawOgre.png is Terrorclaw the Ogre.", "candidatePrefab": "CHAR_Winter_Yeti_VBlood", "candidateGuid": -1347412392, "displayNameEn": "Terrorclaw the Ogre", @@ -1336,7 +1371,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_TristanVampireHunter.png": { "assetName": "Portrait_Large_Normal_TristanVampireHunter.png", @@ -1345,8 +1380,9 @@ "Texture2D/Portrait_Large_Normal_TristanVampireHunter.png", "Sprite/Portrait_Large_Normal_TristanVampireHunter.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_TristanVampireHunter.png is Tristan the Vampire Hunter.", "candidatePrefab": "CHAR_VHunter_Leader_VBlood", "candidateGuid": -1449631170, "displayNameEn": "Tristan the Vampire Hunter", @@ -1357,7 +1393,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_UndeadGeneral.png": { "assetName": "Portrait_Large_Normal_UndeadGeneral.png", @@ -1366,8 +1402,9 @@ "Texture2D/Portrait_Large_Normal_UndeadGeneral.png", "Sprite/Portrait_Large_Normal_UndeadGeneral.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_UndeadGeneral.png is Kriig the Undead General.", "candidatePrefab": "CHAR_Undead_Leader_Vblood", "candidateGuid": -1365931036, "displayNameEn": "Kriig the Undead General", @@ -1378,7 +1415,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_UngoraSpiderQueen.png": { "assetName": "Portrait_Large_Normal_UngoraSpiderQueen.png", @@ -1387,8 +1424,9 @@ "Texture2D/Portrait_Large_Normal_UngoraSpiderQueen.png", "Sprite/Portrait_Large_Normal_UngoraSpiderQueen.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_UngoraSpiderQueen.png is Ungora the Spider Queen.", "candidatePrefab": "CHAR_Spider_Queen_VBlood", "candidateGuid": -548489519, "displayNameEn": "Ungora the Spider Queen", @@ -1399,7 +1437,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Normal_UnholyCommander.png": { "assetName": "Portrait_Large_Normal_UnholyCommander.png", @@ -1480,8 +1518,9 @@ "Texture2D/Portrait_Large_Normal_WingedHorror.png", "Sprite/Portrait_Large_Normal_WingedHorror.png" ], - "joinStatus": "circumstantial", - "approvalStatus": "pending", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_WingedHorror.png is Talzur the Winged Horror.", "candidatePrefab": "CHAR_Manticore_VBlood", "candidateGuid": -393555055, "displayNameEn": "Talzur the Winged Horror", @@ -1492,7 +1531,7 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/VBloodNames.json" ], - "reason": "single best-fit fuzzy title-fragment portrait alias matched current NPC display, prefab, or VBloodNames row" + "reason": "approved user-attested portrait candidate" }, "Portrait_Large_Smoke_AlphaWolf.png": { "assetName": "Portrait_Large_Smoke_AlphaWolf.png", diff --git a/data/enrichment/npc-portrait-map.json b/data/enrichment/npc-portrait-map.json index 57c0f87758..7a16f4173b 100644 --- a/data/enrichment/npc-portrait-map.json +++ b/data/enrichment/npc-portrait-map.json @@ -34,6 +34,40 @@ "data/prefabs/All.json" ] }, + "CHAR_Bandit_Chaosarrow_VBlood": { + "prefab": "CHAR_Bandit_Chaosarrow_VBlood", + "guid": 763273073, + "displayNameEn": "Lidia the Chaos Archer", + "portraitAssetName": "Portrait_Large_Normal_LidiaChaosArcher.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_LidiaChaosArcher.png is Lidia the Chaos Archer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_LidiaChaosArcher.png", + "Sprite/Portrait_Large_Normal_LidiaChaosArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Bandit_Fisherman_VBlood": { + "prefab": "CHAR_Bandit_Fisherman_VBlood", + "guid": -2122682556, + "displayNameEn": "Finn the Fisherman", + "portraitAssetName": "Portrait_Large_Normal_Fisherman.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Fisherman.png is Finn the Fisherman.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Fisherman.png", + "Sprite/Portrait_Large_Normal_Fisherman.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, "CHAR_Bandit_Foreman_VBlood": { "prefab": "CHAR_Bandit_Foreman_VBlood", "guid": 2122229952, @@ -49,6 +83,23 @@ "data/prefabs/All.json" ] }, + "CHAR_Bandit_Frostarrow_VBlood": { + "prefab": "CHAR_Bandit_Frostarrow_VBlood", + "guid": 1124739990, + "displayNameEn": "Keely the Frost Archer", + "portraitAssetName": "Portrait_Large_Normal_KeelyFrostArcher.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_KeelyFrostArcher.png is Keely the Frost Archer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_KeelyFrostArcher.png", + "Sprite/Portrait_Large_Normal_KeelyFrostArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, "CHAR_Bandit_Stalker_VBlood": { "prefab": "CHAR_Bandit_Stalker_VBlood", "guid": 1106149033, @@ -64,6 +115,23 @@ "data/prefabs/All.json" ] }, + "CHAR_Bandit_StoneBreaker_VBlood": { + "prefab": "CHAR_Bandit_StoneBreaker_VBlood", + "guid": -2025101517, + "displayNameEn": "Errol the Stonebreaker", + "portraitAssetName": "Portrait_Large_Normal_ErrolStonebreaker.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ErrolStonebreaker.png is Errol the Stonebreaker.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ErrolStonebreaker.png", + "Sprite/Portrait_Large_Normal_ErrolStonebreaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, "CHAR_Bandit_Tourok_VBlood": { "prefab": "CHAR_Bandit_Tourok_VBlood", "guid": -1659822956, @@ -94,6 +162,295 @@ "data/prefabs/All.json" ] }, + "CHAR_Blackfang_Valyr_VBlood": { + "prefab": "CHAR_Blackfang_Valyr_VBlood", + "guid": 173259239, + "displayNameEn": "Dantos the Forgebinder", + "portraitAssetName": "Portrait_Large_Normal_ForgeBinder.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ForgeBinder.png is Dantos the Forgebinder.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ForgeBinder.png", + "Sprite/Portrait_Large_Normal_ForgeBinder.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_ChurchOfLight_Cardinal_VBlood": { + "prefab": "CHAR_ChurchOfLight_Cardinal_VBlood", + "guid": 114912615, + "displayNameEn": "Azariel the Sunbringer", + "portraitAssetName": "Portrait_Large_Normal_AzarielSunbringer.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_AzarielSunbringer.png is Azariel the Sunbringer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_AzarielSunbringer.png", + "Sprite/Portrait_Large_Normal_AzarielSunbringer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_ChurchOfLight_Overseer_VBlood": { + "prefab": "CHAR_ChurchOfLight_Overseer_VBlood", + "guid": -26105228, + "displayNameEn": "Sir Magnus the Overseer", + "portraitAssetName": "Portrait_Large_Normal_Overseer.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Overseer.png is Sir Magnus the Overseer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Overseer.png", + "Sprite/Portrait_Large_Normal_Overseer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_ChurchOfLight_Paladin_VBlood": { + "prefab": "CHAR_ChurchOfLight_Paladin_VBlood", + "guid": -740796338, + "displayNameEn": "Solarus the Immaculate", + "portraitAssetName": "Portrait_Large_Normal_SolarusImmaculate.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_SolarusImmaculate.png is Solarus the Immaculate.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_SolarusImmaculate.png", + "Sprite/Portrait_Large_Normal_SolarusImmaculate.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_ChurchOfLight_Sommelier_VBlood": { + "prefab": "CHAR_ChurchOfLight_Sommelier_VBlood", + "guid": 192051202, + "displayNameEn": "Baron du Bouchon the Sommelier", + "portraitAssetName": "Portrait_Large_Normal_Sommelier.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Sommelier.png is Baron du Bouchon the Sommelier.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Sommelier.png", + "Sprite/Portrait_Large_Normal_Sommelier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Cursed_MountainBeast_VBlood": { + "prefab": "CHAR_Cursed_MountainBeast_VBlood", + "guid": -1936575244, + "displayNameEn": "Gorecrusher the Behemoth", + "portraitAssetName": "Portrait_Large_Normal_GorecrusherBehemoth.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_GorecrusherBehemoth.png is Gorecrusher the Behemoth.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_GorecrusherBehemoth.png", + "Sprite/Portrait_Large_Normal_GorecrusherBehemoth.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Cursed_ToadKing_VBlood": { + "prefab": "CHAR_Cursed_ToadKing_VBlood", + "guid": -203043163, + "displayNameEn": "Albert the Duke of Balaton", + "portraitAssetName": "Portrait_Large_Normal_DukeBalaton.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_DukeBalaton.png is Albert the Duke of Balaton.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_DukeBalaton.png", + "Sprite/Portrait_Large_Normal_DukeBalaton.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Cursed_Witch_VBlood": { + "prefab": "CHAR_Cursed_Witch_VBlood", + "guid": -910296704, + "displayNameEn": "Matka the Curse Weaver", + "portraitAssetName": "Portrait_Large_Normal_MatkaCurseWeaver.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_MatkaCurseWeaver.png is Matka the Curse Weaver.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MatkaCurseWeaver.png", + "Sprite/Portrait_Large_Normal_MatkaCurseWeaver.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Forest_Bear_Dire_Vblood": { + "prefab": "CHAR_Forest_Bear_Dire_Vblood", + "guid": -1391546313, + "displayNameEn": "Kodia the Ferocious Bear", + "portraitAssetName": "Portrait_Large_Normal_FerociousBear.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_FerociousBear.png is Kodia the Ferocious Bear.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FerociousBear.png", + "Sprite/Portrait_Large_Normal_FerociousBear.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Forest_Wolf_VBlood": { + "prefab": "CHAR_Forest_Wolf_VBlood", + "guid": -1905691330, + "displayNameEn": "Alpha the White Wolf", + "portraitAssetName": "Portrait_Large_Normal_AlphaWolf.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_AlphaWolf.png is Alpha the White Wolf.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_AlphaWolf.png", + "Sprite/Portrait_Large_Normal_AlphaWolf.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Geomancer_Human_VBlood": { + "prefab": "CHAR_Geomancer_Human_VBlood", + "guid": -1065970933, + "displayNameEn": "Terah the Geomancer", + "portraitAssetName": "Portrait_Large_Normal_TerahGeomancer.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_TerahGeomancer.png is Terah the Geomancer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_TerahGeomancer.png", + "Sprite/Portrait_Large_Normal_TerahGeomancer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Gloomrot_Purifier_VBlood": { + "prefab": "CHAR_Gloomrot_Purifier_VBlood", + "guid": 106480588, + "displayNameEn": "Angram the Purifier", + "portraitAssetName": "Portrait_Large_Normal_Purifier.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Purifier.png is Angram the Purifier.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Purifier.png", + "Sprite/Portrait_Large_Normal_Purifier.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Gloomrot_TheProfessor_VBlood": { + "prefab": "CHAR_Gloomrot_TheProfessor_VBlood", + "guid": 814083983, + "displayNameEn": "Henry Blackbrew the Doctor", + "portraitAssetName": "Portrait_Large_Normal_Professor.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Professor.png is Henry Blackbrew the Doctor.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Professor.png", + "Sprite/Portrait_Large_Normal_Professor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Harpy_Matriarch_VBlood": { + "prefab": "CHAR_Harpy_Matriarch_VBlood", + "guid": 685266977, + "displayNameEn": "Morian the Stormwing Matriarch", + "portraitAssetName": "Portrait_Large_Normal_MorianStormwingMatriarch.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_MorianStormwingMatriarch.png is Morian the Stormwing Matriarch.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MorianStormwingMatriarch.png", + "Sprite/Portrait_Large_Normal_MorianStormwingMatriarch.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Manticore_VBlood": { + "prefab": "CHAR_Manticore_VBlood", + "guid": -393555055, + "displayNameEn": "Talzur the Winged Horror", + "portraitAssetName": "Portrait_Large_Normal_WingedHorror.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_WingedHorror.png is Talzur the Winged Horror.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_WingedHorror.png", + "Sprite/Portrait_Large_Normal_WingedHorror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Militia_BishopOfDunley_VBlood": { + "prefab": "CHAR_Militia_BishopOfDunley_VBlood", + "guid": -680831417, + "displayNameEn": "Raziel the Shepherd", + "portraitAssetName": "Portrait_Large_Normal_RazielShepherd.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_RazielShepherd.png is Raziel the Shepherd.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_RazielShepherd.png", + "Sprite/Portrait_Large_Normal_RazielShepherd.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Militia_Glassblower_VBlood": { + "prefab": "CHAR_Militia_Glassblower_VBlood", + "guid": 910988233, + "displayNameEn": "Grethel the Glassblower", + "portraitAssetName": "Portrait_Large_Normal_Glassblower.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Glassblower.png is Grethel the Glassblower.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Glassblower.png", + "Sprite/Portrait_Large_Normal_Glassblower.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, "CHAR_Militia_Guard_VBlood": { "prefab": "CHAR_Militia_Guard_VBlood", "guid": -29797003, @@ -124,6 +481,193 @@ "data/prefabs/All.json" ] }, + "CHAR_Militia_Longbowman_LightArrow_Vblood": { + "prefab": "CHAR_Militia_Longbowman_LightArrow_Vblood", + "guid": 850622034, + "displayNameEn": "Meredith the Bright Archer", + "portraitAssetName": "Portrait_Large_Normal_MeredithBrightArcher.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_MeredithBrightArcher.png is Meredith the Bright Archer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_MeredithBrightArcher.png", + "Sprite/Portrait_Large_Normal_MeredithBrightArcher.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Militia_Nun_VBlood": { + "prefab": "CHAR_Militia_Nun_VBlood", + "guid": -99012450, + "displayNameEn": "Christina the Sun Priestess", + "portraitAssetName": "Portrait_Large_Normal_ChristinaSunPriestess.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ChristinaSunPriestess.png is Christina the Sun Priestess.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ChristinaSunPriestess.png", + "Sprite/Portrait_Large_Normal_ChristinaSunPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Poloma_VBlood": { + "prefab": "CHAR_Poloma_VBlood", + "guid": -484556888, + "displayNameEn": "Polora the Feywalker", + "portraitAssetName": "Portrait_Large_Normal_PoloraFeywalker.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_PoloraFeywalker.png is Polora the Feywalker.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_PoloraFeywalker.png", + "Sprite/Portrait_Large_Normal_PoloraFeywalker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Spider_Queen_VBlood": { + "prefab": "CHAR_Spider_Queen_VBlood", + "guid": -548489519, + "displayNameEn": "Ungora the Spider Queen", + "portraitAssetName": "Portrait_Large_Normal_UngoraSpiderQueen.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_UngoraSpiderQueen.png is Ungora the Spider Queen.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_UngoraSpiderQueen.png", + "Sprite/Portrait_Large_Normal_UngoraSpiderQueen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_ArenaChampion_VBlood": { + "prefab": "CHAR_Undead_ArenaChampion_VBlood", + "guid": -753453016, + "displayNameEn": "Gaius the Cursed Champion", + "portraitAssetName": "Portrait_Large_Normal_ArenaChampion.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_ArenaChampion.png is Gaius the Cursed Champion.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_ArenaChampion.png", + "Sprite/Portrait_Large_Normal_ArenaChampion.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_BishopOfDeath_VBlood": { + "prefab": "CHAR_Undead_BishopOfDeath_VBlood", + "guid": 577478542, + "displayNameEn": "Goreswine the Ravager", + "portraitAssetName": "Portrait_Large_Normal_GoreswineRavager.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_GoreswineRavager.png is Goreswine the Ravager.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_GoreswineRavager.png", + "Sprite/Portrait_Large_Normal_GoreswineRavager.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_BishopOfShadows_VBlood": { + "prefab": "CHAR_Undead_BishopOfShadows_VBlood", + "guid": 939467639, + "displayNameEn": "Leandra the Shadow Priestess", + "portraitAssetName": "Portrait_Large_Normal_LeandraShadowPriestess.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_LeandraShadowPriestess.png is Leandra the Shadow Priestess.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_LeandraShadowPriestess.png", + "Sprite/Portrait_Large_Normal_LeandraShadowPriestess.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_CursedSmith_VBlood": { + "prefab": "CHAR_Undead_CursedSmith_VBlood", + "guid": 326378955, + "displayNameEn": "Cyril the Cursed Smith", + "portraitAssetName": "Portrait_Large_Normal_CursedSmith.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_CursedSmith.png is Cyril the Cursed Smith.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_CursedSmith.png", + "Sprite/Portrait_Large_Normal_CursedSmith.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_Leader_Vblood": { + "prefab": "CHAR_Undead_Leader_Vblood", + "guid": -1365931036, + "displayNameEn": "Kriig the Undead General", + "portraitAssetName": "Portrait_Large_Normal_UndeadGeneral.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_UndeadGeneral.png is Kriig the Undead General.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_UndeadGeneral.png", + "Sprite/Portrait_Large_Normal_UndeadGeneral.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_Priest_VBlood": { + "prefab": "CHAR_Undead_Priest_VBlood", + "guid": 153390636, + "displayNameEn": "Nicholaus the Fallen", + "portraitAssetName": "Portrait_Large_Normal_NicholausFallen.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_NicholausFallen.png is Nicholaus the Fallen.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_NicholausFallen.png", + "Sprite/Portrait_Large_Normal_NicholausFallen.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Undead_ZealousCultist_VBlood": { + "prefab": "CHAR_Undead_ZealousCultist_VBlood", + "guid": -1208888966, + "displayNameEn": "Foulrot the Soultaker", + "portraitAssetName": "Portrait_Large_Normal_FoulrotSoultaker.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_FoulrotSoultaker.png is Foulrot the Soultaker.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FoulrotSoultaker.png", + "Sprite/Portrait_Large_Normal_FoulrotSoultaker.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, "CHAR_Vampire_BloodKnight_VBlood": { "prefab": "CHAR_Vampire_BloodKnight_VBlood", "guid": 495971434, @@ -141,6 +685,23 @@ "data/prefabs/VBloodNames.json" ] }, + "CHAR_Vampire_Dracula_VBlood": { + "prefab": "CHAR_Vampire_Dracula_VBlood", + "guid": -327335305, + "displayNameEn": "Dracula the Immortal King", + "portraitAssetName": "Portrait_Large_Normal_Dracula.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_Dracula.png is Dracula the Immortal King.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_Dracula.png", + "Sprite/Portrait_Large_Normal_Dracula.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, "CHAR_Vampire_IceRanger_VBlood": { "prefab": "CHAR_Vampire_IceRanger_VBlood", "guid": 795262842, @@ -172,6 +733,108 @@ "data/enrichment/npc-classification-map.json", "data/prefabs/All.json" ] + }, + "CHAR_VHunter_Jade_VBlood": { + "prefab": "CHAR_VHunter_Jade_VBlood", + "guid": -1968372384, + "displayNameEn": "Jade the Vampire Hunter", + "portraitAssetName": "Portrait_Large_Normal_JadeVampireHunter.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_JadeVampireHunter.png is Jade the Vampire Hunter.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_JadeVampireHunter.png", + "Sprite/Portrait_Large_Normal_JadeVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_VHunter_Leader_VBlood": { + "prefab": "CHAR_VHunter_Leader_VBlood", + "guid": -1449631170, + "displayNameEn": "Tristan the Vampire Hunter", + "portraitAssetName": "Portrait_Large_Normal_TristanVampireHunter.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_TristanVampireHunter.png is Tristan the Vampire Hunter.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_TristanVampireHunter.png", + "Sprite/Portrait_Large_Normal_TristanVampireHunter.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Villager_CursedWanderer_VBlood": { + "prefab": "CHAR_Villager_CursedWanderer_VBlood", + "guid": 109969450, + "displayNameEn": "Ben the Old Wanderer", + "portraitAssetName": "Portrait_Large_Normal_CursedWanderer.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_CursedWanderer.png is Ben the Old Wanderer.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_CursedWanderer.png", + "Sprite/Portrait_Large_Normal_CursedWanderer.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Villager_Tailor_VBlood": { + "prefab": "CHAR_Villager_Tailor_VBlood", + "guid": -1942352521, + "displayNameEn": "Beatrice the Tailor", + "portraitAssetName": "Portrait_Large_Normal_BeatriceTailor.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_BeatriceTailor.png is Beatrice the Tailor.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_BeatriceTailor.png", + "Sprite/Portrait_Large_Normal_BeatriceTailor.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Wendigo_VBlood": { + "prefab": "CHAR_Wendigo_VBlood", + "guid": 24378719, + "displayNameEn": "Frostmaw the Mountain Terror", + "portraitAssetName": "Portrait_Large_Normal_FrostmawMountainTerror.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_FrostmawMountainTerror.png is Frostmaw the Mountain Terror.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_FrostmawMountainTerror.png", + "Sprite/Portrait_Large_Normal_FrostmawMountainTerror.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] + }, + "CHAR_Winter_Yeti_VBlood": { + "prefab": "CHAR_Winter_Yeti_VBlood", + "guid": -1347412392, + "displayNameEn": "Terrorclaw the Ogre", + "portraitAssetName": "Portrait_Large_Normal_TerrorclawOgre.png", + "portraitAssetFamily": "portrait-large-normal", + "joinStatus": "user-attested", + "approvalStatus": "approved", + "approvalNote": "User-attested approved match: Portrait_Large_Normal_TerrorclawOgre.png is Terrorclaw the Ogre.", + "evidenceRefs": [ + "Texture2D/Portrait_Large_Normal_TerrorclawOgre.png", + "Sprite/Portrait_Large_Normal_TerrorclawOgre.png", + "data/enrichment/npc-display-map.json", + "data/enrichment/npc-classification-map.json", + "data/prefabs/VBloodNames.json" + ] } } } diff --git a/scripts/npc-portraits.test.ts b/scripts/npc-portraits.test.ts index a948695fcc..f52def6325 100644 --- a/scripts/npc-portraits.test.ts +++ b/scripts/npc-portraits.test.ts @@ -114,18 +114,20 @@ test("buildNpcPortraitSnapshots promotes exact and approved user-attested rows o const { candidates, portraitMap } = await buildFixture(assetDumpDir); assert.equal(candidates.entriesByAssetName["CHAR_Bandit_Bomber_VBlood_HeadPortrait.png"].joinStatus, "source-backed"); - assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BeatriceTailor.png"].joinStatus, "circumstantial"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BeatriceTailor.png"].joinStatus, "user-attested"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BeatriceTailor.png"].approvalStatus, "approved"); assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BeatriceTailor.png"].candidatePrefab, "CHAR_Villager_Tailor_VBlood"); assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BloodCommander.png"].joinStatus, "user-attested"); assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_BloodCommander.png"].approvalStatus, "approved"); assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_FrostCommander.png"].candidatePrefab, "CHAR_Vampire_IceRanger_VBlood"); - assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_KeelyFrostArcher.png"].joinStatus, "circumstantial"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_KeelyFrostArcher.png"].joinStatus, "user-attested"); + assert.equal(candidates.entriesByAssetName["Portrait_Large_Normal_KeelyFrostArcher.png"].approvalStatus, "approved"); assert.equal(portraitMap.entriesByPrefab.CHAR_Bandit_Bomber_VBlood.portraitAssetName, "CHAR_Bandit_Bomber_VBlood_HeadPortrait.png"); assert.equal(portraitMap.entriesByPrefab.CHAR_Vampire_BloodKnight_VBlood.joinStatus, "user-attested"); assert.equal(portraitMap.entriesByPrefab.CHAR_Vampire_IceRanger_VBlood.approvalStatus, "approved"); - assert.equal(portraitMap.entriesByPrefab.CHAR_Villager_Tailor_VBlood, undefined); - assert.equal(portraitMap.entriesByPrefab.CHAR_Bandit_Frostarrow_VBlood, undefined); + assert.equal(portraitMap.entriesByPrefab.CHAR_Villager_Tailor_VBlood.approvalStatus, "approved"); + assert.equal(portraitMap.entriesByPrefab.CHAR_Bandit_Frostarrow_VBlood.portraitAssetName, "Portrait_Large_Normal_KeelyFrostArcher.png"); } ); }); @@ -140,14 +142,25 @@ test("buildNpcPortraitSnapshots keeps title-fragment fuzzy matches candidate-onl ], async (assetDumpDir) => { const { candidates, portraitMap } = await buildFixture(assetDumpDir); - const expected = [ + const approvedExpected = [ ["Portrait_Large_Normal_FerociousBear.png", "CHAR_Forest_Bear_Dire_Vblood", "Kodia the Ferocious Bear"], - ["Portrait_Large_Normal_Purifier.png", "CHAR_Gloomrot_Purifier_VBlood", "Angram the Purifier"], + ["Portrait_Large_Normal_Purifier.png", "CHAR_Gloomrot_Purifier_VBlood", "Angram the Purifier"] + ] as const; + const pendingExpected = [ ["Portrait_Small_Normal_DukeBalaton.png", "CHAR_Cursed_ToadKing_VBlood", "Albert the Duke of Balaton"], ["Portrait_Small_Smoke_CursedWanderer.png", "CHAR_Villager_CursedWanderer_VBlood", "Ben the Old Wanderer"] ] as const; - for (const [assetName, prefab, displayNameEn] of expected) { + for (const [assetName, prefab, displayNameEn] of approvedExpected) { + const candidate = candidates.entriesByAssetName[assetName]; + assert.equal(candidate.joinStatus, "user-attested"); + assert.equal(candidate.approvalStatus, "approved"); + assert.equal(candidate.candidatePrefab, prefab); + assert.equal(candidate.displayNameEn, displayNameEn); + assert.equal(portraitMap.entriesByPrefab[prefab].portraitAssetName, assetName); + } + + for (const [assetName, prefab, displayNameEn] of pendingExpected) { const candidate = candidates.entriesByAssetName[assetName]; assert.equal(candidate.joinStatus, "circumstantial"); assert.equal(candidate.approvalStatus, "pending"); diff --git a/scripts/npc-portraits.ts b/scripts/npc-portraits.ts index 94180796b5..e92b1ab4f6 100644 --- a/scripts/npc-portraits.ts +++ b/scripts/npc-portraits.ts @@ -84,6 +84,165 @@ const manualAttestedPortraits: Record = { + "Portrait_Large_Normal_AlphaWolf.png": { + prefab: "CHAR_Forest_Wolf_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_AlphaWolf.png is Alpha the White Wolf." + }, + "Portrait_Large_Normal_ArenaChampion.png": { + prefab: "CHAR_Undead_ArenaChampion_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_ArenaChampion.png is Gaius the Cursed Champion." + }, + "Portrait_Large_Normal_AzarielSunbringer.png": { + prefab: "CHAR_ChurchOfLight_Cardinal_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_AzarielSunbringer.png is Azariel the Sunbringer." + }, + "Portrait_Large_Normal_BeatriceTailor.png": { + prefab: "CHAR_Villager_Tailor_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_BeatriceTailor.png is Beatrice the Tailor." + }, + "Portrait_Large_Normal_CursedSmith.png": { + prefab: "CHAR_Undead_CursedSmith_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_CursedSmith.png is Cyril the Cursed Smith." + }, + "Portrait_Large_Normal_CursedWanderer.png": { + prefab: "CHAR_Villager_CursedWanderer_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_CursedWanderer.png is Ben the Old Wanderer." + }, + "Portrait_Large_Normal_Dracula.png": { + prefab: "CHAR_Vampire_Dracula_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Dracula.png is Dracula the Immortal King." + }, + "Portrait_Large_Normal_DukeBalaton.png": { + prefab: "CHAR_Cursed_ToadKing_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_DukeBalaton.png is Albert the Duke of Balaton." + }, + "Portrait_Large_Normal_ErrolStonebreaker.png": { + prefab: "CHAR_Bandit_StoneBreaker_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_ErrolStonebreaker.png is Errol the Stonebreaker." + }, + "Portrait_Large_Normal_FerociousBear.png": { + prefab: "CHAR_Forest_Bear_Dire_Vblood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_FerociousBear.png is Kodia the Ferocious Bear." + }, + "Portrait_Large_Normal_Fisherman.png": { + prefab: "CHAR_Bandit_Fisherman_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Fisherman.png is Finn the Fisherman." + }, + "Portrait_Large_Normal_ForgeBinder.png": { + prefab: "CHAR_Blackfang_Valyr_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_ForgeBinder.png is Dantos the Forgebinder." + }, + "Portrait_Large_Normal_FoulrotSoultaker.png": { + prefab: "CHAR_Undead_ZealousCultist_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_FoulrotSoultaker.png is Foulrot the Soultaker." + }, + "Portrait_Large_Normal_FrostmawMountainTerror.png": { + prefab: "CHAR_Wendigo_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_FrostmawMountainTerror.png is Frostmaw the Mountain Terror." + }, + "Portrait_Large_Normal_Glassblower.png": { + prefab: "CHAR_Militia_Glassblower_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Glassblower.png is Grethel the Glassblower." + }, + "Portrait_Large_Normal_GorecrusherBehemoth.png": { + prefab: "CHAR_Cursed_MountainBeast_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_GorecrusherBehemoth.png is Gorecrusher the Behemoth." + }, + "Portrait_Large_Normal_GoreswineRavager.png": { + prefab: "CHAR_Undead_BishopOfDeath_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_GoreswineRavager.png is Goreswine the Ravager." + }, + "Portrait_Large_Normal_JadeVampireHunter.png": { + prefab: "CHAR_VHunter_Jade_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_JadeVampireHunter.png is Jade the Vampire Hunter." + }, + "Portrait_Large_Normal_KeelyFrostArcher.png": { + prefab: "CHAR_Bandit_Frostarrow_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_KeelyFrostArcher.png is Keely the Frost Archer." + }, + "Portrait_Large_Normal_LeandraShadowPriestess.png": { + prefab: "CHAR_Undead_BishopOfShadows_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_LeandraShadowPriestess.png is Leandra the Shadow Priestess." + }, + "Portrait_Large_Normal_LidiaChaosArcher.png": { + prefab: "CHAR_Bandit_Chaosarrow_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_LidiaChaosArcher.png is Lidia the Chaos Archer." + }, + "Portrait_Large_Normal_MatkaCurseWeaver.png": { + prefab: "CHAR_Cursed_Witch_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_MatkaCurseWeaver.png is Matka the Curse Weaver." + }, + "Portrait_Large_Normal_MeredithBrightArcher.png": { + prefab: "CHAR_Militia_Longbowman_LightArrow_Vblood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_MeredithBrightArcher.png is Meredith the Bright Archer." + }, + "Portrait_Large_Normal_MorianStormwingMatriarch.png": { + prefab: "CHAR_Harpy_Matriarch_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_MorianStormwingMatriarch.png is Morian the Stormwing Matriarch." + }, + "Portrait_Large_Normal_NicholausFallen.png": { + prefab: "CHAR_Undead_Priest_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_NicholausFallen.png is Nicholaus the Fallen." + }, + "Portrait_Large_Normal_Overseer.png": { + prefab: "CHAR_ChurchOfLight_Overseer_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Overseer.png is Sir Magnus the Overseer." + }, + "Portrait_Large_Normal_PoloraFeywalker.png": { + prefab: "CHAR_Poloma_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_PoloraFeywalker.png is Polora the Feywalker." + }, + "Portrait_Large_Normal_Professor.png": { + prefab: "CHAR_Gloomrot_TheProfessor_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Professor.png is Henry Blackbrew the Doctor." + }, + "Portrait_Large_Normal_Purifier.png": { + prefab: "CHAR_Gloomrot_Purifier_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Purifier.png is Angram the Purifier." + }, + "Portrait_Large_Normal_RazielShepherd.png": { + prefab: "CHAR_Militia_BishopOfDunley_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_RazielShepherd.png is Raziel the Shepherd." + }, + "Portrait_Large_Normal_SolarusImmaculate.png": { + prefab: "CHAR_ChurchOfLight_Paladin_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_SolarusImmaculate.png is Solarus the Immaculate." + }, + "Portrait_Large_Normal_Sommelier.png": { + prefab: "CHAR_ChurchOfLight_Sommelier_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_Sommelier.png is Baron du Bouchon the Sommelier." + }, + "Portrait_Large_Normal_ChristinaSunPriestess.png": { + prefab: "CHAR_Militia_Nun_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_ChristinaSunPriestess.png is Christina the Sun Priestess." + }, + "Portrait_Large_Normal_TerahGeomancer.png": { + prefab: "CHAR_Geomancer_Human_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_TerahGeomancer.png is Terah the Geomancer." + }, + "Portrait_Large_Normal_TerrorclawOgre.png": { + prefab: "CHAR_Winter_Yeti_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_TerrorclawOgre.png is Terrorclaw the Ogre." + }, + "Portrait_Large_Normal_TristanVampireHunter.png": { + prefab: "CHAR_VHunter_Leader_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_TristanVampireHunter.png is Tristan the Vampire Hunter." + }, + "Portrait_Large_Normal_UndeadGeneral.png": { + prefab: "CHAR_Undead_Leader_Vblood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_UndeadGeneral.png is Kriig the Undead General." + }, + "Portrait_Large_Normal_UngoraSpiderQueen.png": { + prefab: "CHAR_Spider_Queen_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_UngoraSpiderQueen.png is Ungora the Spider Queen." + }, + "Portrait_Large_Normal_WingedHorror.png": { + prefab: "CHAR_Manticore_VBlood", + approvalNote: "User-attested approved match: Portrait_Large_Normal_WingedHorror.png is Talzur the Winged Horror." + } +}; + export const unsafeNpcPortraitPrefabPattern = /(?:GateBoss|Primal|Minion|(?:^|_)Tail(?:_|$)|ShadowClone|_UNUSED)/i; const stopWords = new Set(["the", "of"]); @@ -296,6 +455,30 @@ function classifyAsset(asset: AssetRecord, currentRows: Map !unsafeReasonForPrefab(row.prefab)); const matches = rowsEligibleForMatching.filter((row) => row.aliases.has(normalizedSubject)); + const fuzzyMatches = matches.length === 0 ? rowsEligibleForMatching.filter((row) => row.fuzzyAliases.has(normalizedSubject)) : []; + + const approved = userApprovedPortraitCandidates[asset.assetName]; + if (approved) { + const row = currentRows.get(approved.prefab); + const matchedRow = matches.length === 1 ? matches[0] : fuzzyMatches.length === 1 ? fuzzyMatches[0] : undefined; + const unsafeReason = unsafeReasonForPrefab(approved.prefab); + if (row && matchedRow?.prefab === row.prefab && !unsafeReason) { + return { + assetName: asset.assetName, + assetFamily: asset.assetFamily, + assetSourceRefs: asset.sourceRefs, + joinStatus: "user-attested", + approvalStatus: "approved", + approvalNote: approved.approvalNote, + candidatePrefab: row.prefab, + candidateGuid: row.guid, + displayNameEn: row.displayNameEn, + evidenceRefs: [...evidenceRefs, "data/prefabs/VBloodNames.json"], + reason: "approved user-attested portrait candidate" + }; + } + } + if (matches.length === 1) { const row = matches[0]; return { @@ -312,7 +495,6 @@ function classifyAsset(asset: AssetRecord, currentRows: Map row.fuzzyAliases.has(normalizedSubject)) : []; if (fuzzyMatches.length === 1) { const row = fuzzyMatches[0]; return { From faeefd81608dd9534a60896e64542e85fbb59c2b Mon Sep 17 00:00:00 2001 From: mfoltz Date: Wed, 6 May 2026 18:16:55 -0500 Subject: [PATCH 6/6] Fix NPC portrait low-signal coverage count --- data/enrichment/enrichment-coverage.json | 2 +- scripts/refresh-db-assets.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/enrichment/enrichment-coverage.json b/data/enrichment/enrichment-coverage.json index ac298b9777..7ac98338aa 100644 --- a/data/enrichment/enrichment-coverage.json +++ b/data/enrichment/enrichment-coverage.json @@ -67,7 +67,7 @@ "matched": 50, "coveragePct": 0.7246, "signal": "high-signal", - "lowSignalExcluded": 47 + "lowSignalExcluded": 0 }, "quest-display-map": { "total": 163, diff --git a/scripts/refresh-db-assets.ts b/scripts/refresh-db-assets.ts index 48115ac6b6..0878ac2903 100644 --- a/scripts/refresh-db-assets.ts +++ b/scripts/refresh-db-assets.ts @@ -3494,9 +3494,10 @@ async function main() { (entry.level !== undefined || Boolean(entry.bloodType) || Boolean(entry.faction) || Boolean(entry.unitCategory) || entry.isVBlood === true || entry.isServant === true) && isLowSignalSource(entry.sourceKind) ).length; + const npcPortraitMappedPrefabs = new Set(Object.keys(stableNpcPortraitSnapshots.portraitMap.entriesByPrefab)); const npcPortraitLowSignalPrefabs = new Set( Object.values(stableNpcPortraitSnapshots.candidates.entriesByAssetName).flatMap((entry) => - entry.joinStatus === "circumstantial" && entry.candidatePrefab ? [entry.candidatePrefab] : [] + entry.joinStatus === "circumstantial" && entry.candidatePrefab && !npcPortraitMappedPrefabs.has(entry.candidatePrefab) ? [entry.candidatePrefab] : [] ) ).size;