Update Healer Man server

This commit is contained in:
phenom committed 2026-10-07 13:05:04 -04:00
1 parent 69809f7228
commit ff20ab8779
101 files changed
+16713 -945

No files matched your search

@@ -302,7 +302,9 @@ function textureSizeByIndex(json, images) {
});
}
export async function auditGeometry(file, textureSizes, label = path.basename(file)) {
export async function auditGeometry(file, textureSizes, label = path.basename(file), options = {}) {
const hotspotCellSizeM = Number(options.hotspotCellSizeM);
const collectHotspots = Number.isFinite(hotspotCellSizeM) && hotspotCellSizeM > 0;
const io = new NodeIO()
.registerExtensions(ALL_EXTENSIONS)
.registerDependencies({ "meshopt.decoder": MeshoptDecoder });
@@ -325,6 +327,7 @@ export async function auditGeometry(file, textureSizes, label = path.basename(fi
untexturedMaterials: {},
};
const bySurface = { floor: emptySurfaceMetrics(), wall: emptySurfaceMetrics(), other: emptySurfaceMetrics() };
const hotspotCells = new Map();
for (const scene of document.getRoot().listScenes()) {
const sceneNodes = [];
const visit = (node) => {
@@ -400,19 +403,59 @@ export async function auditGeometry(file, textureSizes, label = path.basename(fi
info.getExtension("KHR_texture_transform"),
));
const mapping = mappingMetrics(points, uvValues, size[0], size[1]);
if (!mapping || mapping.uvDoubleArea <= 1e-12) {
const uvDegenerate = !mapping || mapping.uvDoubleArea <= 1e-12;
const rankCollapsed = uvDegenerate || mapping.smallestStretchPxPerMeter <= 0.01;
if (uvDegenerate) {
for (const metric of [totals, surface]) {
metric.uvDegenerateTriangles += 1;
metric.rankCollapsedTriangles += 1;
metric.uvDegenerateAreaM2 += area;
metric.rankCollapsedAreaM2 += area;
}
} else if (mapping.smallestStretchPxPerMeter <= 0.01) {
} else if (rankCollapsed) {
for (const metric of [totals, surface]) {
metric.rankCollapsedTriangles += 1;
metric.rankCollapsedAreaM2 += area;
}
}
if (collectHotspots && rankCollapsed) {
const centroid = [0, 1, 2].map((axis) => points.reduce((sum, point) => sum + point[axis], 0) / 3);
const cellX = Math.floor(centroid[0] / hotspotCellSizeM);
const cellZ = Math.floor(centroid[2] / hotspotCellSizeM);
const key = `${cellX}:${cellZ}`;
const cell = hotspotCells.get(key) ?? {
cellX,
cellZ,
cellSizeM: hotspotCellSizeM,
uvDegenerateTriangles: 0,
rankCollapsedTriangles: 0,
uvDegenerateAreaM2: 0,
rankCollapsedAreaM2: 0,
centroidAreaWeight: 0,
centroidAreaSum: [0, 0, 0],
bySurface: {},
};
const surfaceCell = cell.bySurface[surfaceKey] ?? {
uvDegenerateTriangles: 0,
rankCollapsedTriangles: 0,
uvDegenerateAreaM2: 0,
rankCollapsedAreaM2: 0,
};
cell.rankCollapsedTriangles += 1;
cell.rankCollapsedAreaM2 += area;
surfaceCell.rankCollapsedTriangles += 1;
surfaceCell.rankCollapsedAreaM2 += area;
if (uvDegenerate) {
cell.uvDegenerateTriangles += 1;
cell.uvDegenerateAreaM2 += area;
surfaceCell.uvDegenerateTriangles += 1;
surfaceCell.uvDegenerateAreaM2 += area;
}
cell.centroidAreaWeight += area;
for (let axis = 0; axis < 3; axis += 1) cell.centroidAreaSum[axis] += centroid[axis] * area;
cell.bySurface[surfaceKey] = surfaceCell;
hotspotCells.set(key, cell);
}
}
}
}
@@ -428,7 +471,31 @@ export async function auditGeometry(file, textureSizes, label = path.basename(fi
for (const material of Object.values(materialCoverage.untexturedMaterials)) {
material.worldAreaM2 = round(material.worldAreaM2, 2);
}
return { ...totals, bySurface, materialCoverage };
const uvHotspots = collectHotspots
? [...hotspotCells.values()]
.map((cell) => ({
cellX: cell.cellX,
cellZ: cell.cellZ,
cellSizeM: cell.cellSizeM,
center: [
round((cell.cellX + 0.5) * cell.cellSizeM, 2),
round(cell.centroidAreaSum[1] / cell.centroidAreaWeight, 2),
round((cell.cellZ + 0.5) * cell.cellSizeM, 2),
],
uvDegenerateTriangles: cell.uvDegenerateTriangles,
rankCollapsedTriangles: cell.rankCollapsedTriangles,
uvDegenerateAreaM2: round(cell.uvDegenerateAreaM2, 2),
rankCollapsedAreaM2: round(cell.rankCollapsedAreaM2, 2),
bySurface: Object.fromEntries(Object.entries(cell.bySurface).map(([surface, metric]) => [surface, {
uvDegenerateTriangles: metric.uvDegenerateTriangles,
rankCollapsedTriangles: metric.rankCollapsedTriangles,
uvDegenerateAreaM2: round(metric.uvDegenerateAreaM2, 2),
rankCollapsedAreaM2: round(metric.rankCollapsedAreaM2, 2),
}])),
}))
.sort((left, right) => right.rankCollapsedAreaM2 - left.rankCollapsedAreaM2 || left.cellX - right.cellX || left.cellZ - right.cellZ)
: undefined;
return { ...totals, bySurface, materialCoverage, ...(uvHotspots ? { uvHotspots } : {}) };
}
function compareImages(source, shipped) {
@@ -479,6 +546,39 @@ function delta(after, before, key) {
return round(after[key] - before[key], 2);
}
function compareUvHotspots(sourceCells = [], runtimeCells = []) {
const sourceByCell = new Map(sourceCells.map((cell) => [`${cell.cellX}:${cell.cellZ}`, cell]));
const runtimeByCell = new Map(runtimeCells.map((cell) => [`${cell.cellX}:${cell.cellZ}`, cell]));
const keys = new Set([...sourceByCell.keys(), ...runtimeByCell.keys()]);
return [...keys].map((key) => {
const source = sourceByCell.get(key);
const runtime = runtimeByCell.get(key);
const center = runtime?.center ?? source?.center;
const sourceArea = source?.rankCollapsedAreaM2 ?? 0;
const runtimeArea = runtime?.rankCollapsedAreaM2 ?? 0;
const sourceUvArea = source?.uvDegenerateAreaM2 ?? 0;
const runtimeUvArea = runtime?.uvDegenerateAreaM2 ?? 0;
return {
cellX: runtime?.cellX ?? source.cellX,
cellZ: runtime?.cellZ ?? source.cellZ,
cellSizeM: runtime?.cellSizeM ?? source.cellSizeM,
center,
rankCollapsedAreaM2Delta: round(runtimeArea - sourceArea, 2),
uvDegenerateAreaM2Delta: round(runtimeUvArea - sourceUvArea, 2),
rankCollapsedTrianglesDelta: (runtime?.rankCollapsedTriangles ?? 0) - (source?.rankCollapsedTriangles ?? 0),
uvDegenerateTrianglesDelta: (runtime?.uvDegenerateTriangles ?? 0) - (source?.uvDegenerateTriangles ?? 0),
sourceRankCollapsedAreaM2: sourceArea,
runtimeRankCollapsedAreaM2: runtimeArea,
sourceUvDegenerateAreaM2: sourceUvArea,
runtimeUvDegenerateAreaM2: runtimeUvArea,
};
})
.filter((cell) => cell.rankCollapsedAreaM2Delta > 0 || cell.uvDegenerateAreaM2Delta > 0)
.sort((left, right) => right.rankCollapsedAreaM2Delta - left.rankCollapsedAreaM2Delta
|| right.uvDegenerateAreaM2Delta - left.uvDegenerateAreaM2Delta)
.slice(0, 50);
}
function resolvePublishedPath(url) {
if (typeof url !== "string" || !url.startsWith("/assets/game/")) return null;
const file = path.resolve(publicRoot, url.replace(/^\//u, ""));
@@ -511,7 +611,7 @@ async function readWowDefinitions() {
return definitions;
}
async function auditDungeon(definition, io, visualOverride = null) {
async function auditDungeon(definition, io, visualOverride = null, hotspotCellSizeM = null) {
const visualEntry = definition.assets?.visual?.[0];
const authoredRuntimePath = definition.id === "wailing-caverns"
? path.join(projectRoot, "src/assets/game/dungeons/wailing-caverns/wailing-caverns-visual.glb")
@@ -542,12 +642,12 @@ async function auditDungeon(definition, io, visualOverride = null) {
row.shipped = await auditGlb(publishedPath, `${definition.id} shipped`);
row.shippedGeometry = await auditGeometry(publishedPath,
textureSizeByIndex(parseGlb(await readFile(publishedPath), `${definition.id} shipped`).json, row.shipped.images),
`${definition.id} shipped`);
`${definition.id} shipped`, { hotspotCellSizeM });
if (source.path) {
const sourceAudit = await auditGlb(source.path, `${definition.id} source`);
const sourceParsed = parseGlb(await readFile(source.path), `${definition.id} source`);
row.source = sourceAudit;
row.sourceGeometry = await auditGeometry(source.path, textureSizeByIndex(sourceParsed.json, sourceAudit.images), `${definition.id} source`);
row.sourceGeometry = await auditGeometry(source.path, textureSizeByIndex(sourceParsed.json, sourceAudit.images), `${definition.id} source`, { hotspotCellSizeM });
row.textureComparison = compareImages(sourceAudit, row.shipped);
row.geometryComparison = {
triangleCountDelta: row.shippedGeometry.triangleCount - row.sourceGeometry.triangleCount,
@@ -577,6 +677,7 @@ async function auditDungeon(definition, io, visualOverride = null) {
rankCollapsedAreaM2Delta: round(shippedMetric.rankCollapsedAreaM2 - sourceMetric.rankCollapsedAreaM2, 2),
}];
})),
...(hotspotCellSizeM ? { uvHotspots: compareUvHotspots(row.sourceGeometry.uvHotspots, row.shippedGeometry.uvHotspots) } : {}),
};
if (row.textureComparison.downsizedImages) {
row.issues.push(`${row.textureComparison.downsizedImages} shipped images are smaller than their matched source dimensions.`);
@@ -680,6 +781,11 @@ async function main() {
const overrideId = overrideSeparator > 0 ? overrideText.slice(0, overrideSeparator) : null;
const visualOverride = overrideId ? path.resolve(projectRoot, overrideText.slice(overrideSeparator + 1)) : null;
if (overrideText && !visualOverride) throw new Error("--visual-override must be <dungeon-id>=<glb-path>.");
const hotspotArg = process.argv.indexOf("--hotspot-cell-m");
const hotspotCellSizeM = hotspotArg >= 0 ? Number(process.argv[hotspotArg + 1]) : null;
if (hotspotArg >= 0 && (!Number.isFinite(hotspotCellSizeM) || hotspotCellSizeM <= 0)) {
throw new Error("--hotspot-cell-m must be a positive world-space cell size in meters.");
}
const selectedIds = new Set([...(onlyIds ?? []), ...(overrideId ? [overrideId] : [])]);
const unknownIds = [...selectedIds].filter((id) => !allDefinitions.some((definition) => definition.id === id));
if (unknownIds.length) throw new Error(`Unknown WoW dungeon id(s): ${unknownIds.join(", ")}.`);
@@ -690,7 +796,7 @@ async function main() {
const dungeons = [];
for (const [index, definition] of definitions.entries()) {
console.log(`[${index + 1}/${definitions.length}] auditing ${definition.id}`);
dungeons.push(await auditDungeon(definition, io, definition.id === overrideId ? visualOverride : null));
dungeons.push(await auditDungeon(definition, io, definition.id === overrideId ? visualOverride : null, hotspotCellSizeM));
}
const compared = dungeons.filter((dungeon) => dungeon.source && dungeon.shipped);
const shipped = dungeons.filter((dungeon) => dungeon.shipped);