Release Healer Man 0.1.7

This commit is contained in:
phenom
2026-08-22 15:53:43 -04:00
parent b12fb84859
commit eca6b5db9b
99 changed files with 1931 additions and 150 deletions
@@ -0,0 +1,121 @@
#!/usr/bin/env node
import { access, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { analyzeGlbFile } from "./glb-performance.mjs";
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const baselineFile = path.join(
projectRoot,
"scripts/asset-pipeline/environment-performance-baseline.json",
);
const defaultRoots = [
path.join(projectRoot, "public/assets/game/manastorm"),
path.join(projectRoot, "src/assets/game/dungeons"),
];
const defaultThresholds = {
maxEstimatedDrawCalls: 1_500,
maxUnbatchedDrawCallSavings: 250,
};
async function exists(file) {
try {
await access(file);
return true;
} catch {
return false;
}
}
async function collectVisualGlbs(root, files = []) {
if (!await exists(root)) return files;
const entry = await stat(root);
if (entry.isFile()) {
if (/(?:^|[-_])visual\.glb$/i.test(path.basename(root))) files.push(root);
return files;
}
for (const child of await readdir(root, { withFileTypes: true })) {
const target = path.join(root, child.name);
if (child.isDirectory()) await collectVisualGlbs(target, files);
else if (/(?:^|[-_])visual\.glb$/i.test(child.name)) files.push(target);
}
return files;
}
function projectPath(file) {
return path.relative(projectRoot, file).split(path.sep).join("/");
}
function exceeds(report, limits) {
return report.estimatedDrawCalls > limits.maxEstimatedDrawCalls
|| report.unbatchedDrawCallSavings > limits.maxUnbatchedDrawCallSavings;
}
const args = process.argv.slice(2);
const writeBaseline = args.includes("--write-baseline");
const roots = args.filter((argument) => argument !== "--write-baseline")
.map((argument) => path.resolve(projectRoot, argument));
const files = [...new Set((await Promise.all(
(roots.length ? roots : defaultRoots).map((root) => collectVisualGlbs(root)),
)).flat())].sort();
const reports = await Promise.all(files.map(async (file) => ({
file: projectPath(file),
byteLength: (await stat(file)).size,
...(await analyzeGlbFile(file)),
})));
if (writeBaseline) {
const baseline = {
schemaVersion: 1,
thresholds: defaultThresholds,
exceptions: Object.fromEntries(
reports
.filter((report) => exceeds(report, defaultThresholds))
.map((report) => [report.file, {
maxEstimatedDrawCalls: Math.max(defaultThresholds.maxEstimatedDrawCalls, report.estimatedDrawCalls),
maxUnbatchedDrawCallSavings: Math.max(
defaultThresholds.maxUnbatchedDrawCallSavings,
report.unbatchedDrawCallSavings,
),
}]),
),
};
await mkdir(path.dirname(baselineFile), { recursive: true });
await writeFile(baselineFile, `${JSON.stringify(baseline, null, 2)}\n`, "utf8");
console.log(`Wrote ${Object.keys(baseline.exceptions).length} performance exceptions to ${projectPath(baselineFile)}.`);
process.exit(0);
}
if (!await exists(baselineFile)) {
throw new Error(`Missing ${projectPath(baselineFile)}. Run this script with --write-baseline.`);
}
const baseline = JSON.parse(await readFile(baselineFile, "utf8"));
const thresholds = { ...defaultThresholds, ...baseline.thresholds };
const regressions = reports.filter((report) => {
const limits = { ...thresholds, ...baseline.exceptions?.[report.file] };
return exceeds(report, limits);
});
const debt = reports.filter((report) => exceeds(report, thresholds));
const top = [...reports]
.sort((left, right) => right.estimatedDrawCalls - left.estimatedDrawCalls)
.slice(0, 12)
.map((report) => ({
asset: report.file,
calls: report.estimatedDrawCalls,
avoidable: report.unbatchedDrawCallSavings,
gpuBatches: report.gpuInstanceBatches,
nodes: report.nodes,
}));
console.table(top);
console.log(JSON.stringify({
status: regressions.length ? "blocked" : "green",
assets: reports.length,
knownBudgetExceptions: debt.length,
regressions: regressions.map((report) => ({
file: report.file,
estimatedDrawCalls: report.estimatedDrawCalls,
unbatchedDrawCallSavings: report.unbatchedDrawCallSavings,
})),
}, null, 2));
if (regressions.length) process.exitCode = 1;
@@ -0,0 +1,249 @@
{
"schemaVersion": 1,
"thresholds": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 250
},
"exceptions": {
"public/assets/game/manastorm/109-sunkentemple/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 320
},
"public/assets/game/manastorm/129-razorfendowns/visual.glb": {
"maxEstimatedDrawCalls": 1507,
"maxUnbatchedDrawCallSavings": 1406
},
"public/assets/game/manastorm/1771-custom-marsh/visual.glb": {
"maxEstimatedDrawCalls": 2493,
"maxUnbatchedDrawCallSavings": 2114
},
"public/assets/game/manastorm/1774-custom-tropical/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 910
},
"public/assets/game/manastorm/1775-custom-tuskarr/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 500
},
"public/assets/game/manastorm/1776-custom-vrykul1/visual.glb": {
"maxEstimatedDrawCalls": 2223,
"maxUnbatchedDrawCallSavings": 1818
},
"public/assets/game/manastorm/1781-forgottenmine/visual.glb": {
"maxEstimatedDrawCalls": 5756,
"maxUnbatchedDrawCallSavings": 3818
},
"public/assets/game/manastorm/1785-valsharaharena/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 392
},
"public/assets/game/manastorm/1787-baradinholdarena/visual.glb": {
"maxEstimatedDrawCalls": 10462,
"maxUnbatchedDrawCallSavings": 9362
},
"public/assets/game/manastorm/1794-twistingnether/visual.glb": {
"maxEstimatedDrawCalls": 3077,
"maxUnbatchedDrawCallSavings": 2557
},
"public/assets/game/manastorm/209-tanarisinstance/visual.glb": {
"maxEstimatedDrawCalls": 4579,
"maxUnbatchedDrawCallSavings": 3547
},
"public/assets/game/manastorm/229-blackrockspire/visual.glb": {
"maxEstimatedDrawCalls": 3972,
"maxUnbatchedDrawCallSavings": 3598
},
"public/assets/game/manastorm/269-cavernsoftime/visual.glb": {
"maxEstimatedDrawCalls": 5446,
"maxUnbatchedDrawCallSavings": 5289
},
"public/assets/game/manastorm/289-schoolofnecromancy/visual.glb": {
"maxEstimatedDrawCalls": 5140,
"maxUnbatchedDrawCallSavings": 4790
},
"public/assets/game/manastorm/309-zul-gurub/visual.glb": {
"maxEstimatedDrawCalls": 17377,
"maxUnbatchedDrawCallSavings": 15605
},
"public/assets/game/manastorm/329-stratholme/visual.glb": {
"maxEstimatedDrawCalls": 2954,
"maxUnbatchedDrawCallSavings": 2725
},
"public/assets/game/manastorm/34-stormwindjail/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 487
},
"public/assets/game/manastorm/349-mauradon/visual.glb": {
"maxEstimatedDrawCalls": 5624,
"maxUnbatchedDrawCallSavings": 5352
},
"public/assets/game/manastorm/36-deadminesinstance/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 309
},
"public/assets/game/manastorm/389-orgrimmarinstance/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 881
},
"public/assets/game/manastorm/429-diremaul/visual.glb": {
"maxEstimatedDrawCalls": 5021,
"maxUnbatchedDrawCallSavings": 4636
},
"public/assets/game/manastorm/469-blackwinglair/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 452
},
"public/assets/game/manastorm/47-razorfenkraulinstance/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 897
},
"public/assets/game/manastorm/48-blackfathom/visual.glb": {
"maxEstimatedDrawCalls": 2164,
"maxUnbatchedDrawCallSavings": 1950
},
"public/assets/game/manastorm/509-ahnqiraj/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 600
},
"public/assets/game/manastorm/531-ahnqirajtemple/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 953
},
"public/assets/game/manastorm/532-karazahn/visual.glb": {
"maxEstimatedDrawCalls": 8540,
"maxUnbatchedDrawCallSavings": 7892
},
"public/assets/game/manastorm/533-stratholme-raid/visual.glb": {
"maxEstimatedDrawCalls": 4165,
"maxUnbatchedDrawCallSavings": 4014
},
"public/assets/game/manastorm/540-hellfiremilitary/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 1150
},
"public/assets/game/manastorm/542-hellfiredemon/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 444
},
"public/assets/game/manastorm/543-hellfirerampart/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 296
},
"public/assets/game/manastorm/545-coilfangpumping/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 674
},
"public/assets/game/manastorm/546-coilfangmarsh/visual.glb": {
"maxEstimatedDrawCalls": 3104,
"maxUnbatchedDrawCallSavings": 3033
},
"public/assets/game/manastorm/547-coilfangdraenei/visual.glb": {
"maxEstimatedDrawCalls": 1535,
"maxUnbatchedDrawCallSavings": 1455
},
"public/assets/game/manastorm/548-coilfangraid/visual.glb": {
"maxEstimatedDrawCalls": 1916,
"maxUnbatchedDrawCallSavings": 1683
},
"public/assets/game/manastorm/550-tempestkeepraid/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 420
},
"public/assets/game/manastorm/552-tempestkeeparcane/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 999
},
"public/assets/game/manastorm/553-tempestkeepatrium/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 1115
},
"public/assets/game/manastorm/554-tempestkeepfactory/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 872
},
"public/assets/game/manastorm/555-auchindounshadow/visual.glb": {
"maxEstimatedDrawCalls": 2624,
"maxUnbatchedDrawCallSavings": 2516
},
"public/assets/game/manastorm/556-auchindoundemon/visual.glb": {
"maxEstimatedDrawCalls": 2095,
"maxUnbatchedDrawCallSavings": 1987
},
"public/assets/game/manastorm/557-auchindounethereal/visual.glb": {
"maxEstimatedDrawCalls": 3164,
"maxUnbatchedDrawCallSavings": 3026
},
"public/assets/game/manastorm/558-auchindoundraenei/visual.glb": {
"maxEstimatedDrawCalls": 2613,
"maxUnbatchedDrawCallSavings": 2523
},
"public/assets/game/manastorm/560-hillsbradpast/visual.glb": {
"maxEstimatedDrawCalls": 28259,
"maxUnbatchedDrawCallSavings": 24733
},
"public/assets/game/manastorm/568-zulaman/visual.glb": {
"maxEstimatedDrawCalls": 4307,
"maxUnbatchedDrawCallSavings": 3850
},
"public/assets/game/manastorm/574-valgarde70/visual.glb": {
"maxEstimatedDrawCalls": 8271,
"maxUnbatchedDrawCallSavings": 7072
},
"public/assets/game/manastorm/575-utgardepinnacle/visual.glb": {
"maxEstimatedDrawCalls": 16277,
"maxUnbatchedDrawCallSavings": 14018
},
"public/assets/game/manastorm/576-nexus70/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 396
},
"public/assets/game/manastorm/578-nexus80/visual.glb": {
"maxEstimatedDrawCalls": 2283,
"maxUnbatchedDrawCallSavings": 1896
},
"public/assets/game/manastorm/585-sunwell5manfix/visual.glb": {
"maxEstimatedDrawCalls": 1810,
"maxUnbatchedDrawCallSavings": 1618
},
"public/assets/game/manastorm/595-stratholmecot/visual.glb": {
"maxEstimatedDrawCalls": 4353,
"maxUnbatchedDrawCallSavings": 3386
},
"public/assets/game/manastorm/600-draktheronkeep/visual.glb": {
"maxEstimatedDrawCalls": 3518,
"maxUnbatchedDrawCallSavings": 2876
},
"public/assets/game/manastorm/602-ulduar80/visual.glb": {
"maxEstimatedDrawCalls": 1973,
"maxUnbatchedDrawCallSavings": 1295
},
"public/assets/game/manastorm/619-azjol-lowercity/visual.glb": {
"maxEstimatedDrawCalls": 1596,
"maxUnbatchedDrawCallSavings": 1243
},
"public/assets/game/manastorm/658-quarryoftears/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 618
},
"public/assets/game/manastorm/668-hallsofreflection/visual.glb": {
"maxEstimatedDrawCalls": 5464,
"maxUnbatchedDrawCallSavings": 4606
},
"public/assets/game/manastorm/70-uldaman/visual.glb": {
"maxEstimatedDrawCalls": 3268,
"maxUnbatchedDrawCallSavings": 3115
},
"public/assets/game/manastorm/821-tinketechshowdown/visual.glb": {
"maxEstimatedDrawCalls": 2585,
"maxUnbatchedDrawCallSavings": 2379
},
"public/assets/game/manastorm/90-gnomeragoninstance/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 1051
},
"public/assets/game/manastorm/908-alvaencounter/visual.glb": {
"maxEstimatedDrawCalls": 1500,
"maxUnbatchedDrawCallSavings": 1096
}
}
}
@@ -0,0 +1,98 @@
import { open } from "node:fs/promises";
const GLB_MAGIC = 0x46546c67;
const JSON_CHUNK_TYPE = 0x4e4f534a;
const GPU_INSTANCING_EXTENSION = "EXT_mesh_gpu_instancing";
function primitiveCount(json, meshIndex) {
return json.meshes?.[meshIndex]?.primitives?.length ?? 0;
}
function gpuInstanceCount(json, node) {
const attributes = node.extensions?.[GPU_INSTANCING_EXTENSION]?.attributes;
if (!attributes || typeof attributes !== "object") return 0;
return Math.max(
0,
...Object.values(attributes).map((accessorIndex) => (
json.accessors?.[accessorIndex]?.count ?? 0
)),
);
}
export function analyzeGlbJson(json) {
const repeatedMeshes = new Map();
let estimatedDrawCalls = 0;
let expandedPrimitiveInstances = 0;
let meshNodes = 0;
let gpuInstanceBatches = 0;
let gpuInstances = 0;
for (const node of json.nodes ?? []) {
if (!Number.isInteger(node.mesh)) continue;
meshNodes += 1;
const primitives = primitiveCount(json, node.mesh);
const instances = gpuInstanceCount(json, node);
estimatedDrawCalls += primitives;
expandedPrimitiveInstances += primitives * Math.max(1, instances);
if (instances > 0) {
gpuInstanceBatches += 1;
gpuInstances += instances;
continue;
}
repeatedMeshes.set(node.mesh, (repeatedMeshes.get(node.mesh) ?? 0) + 1);
}
let eligibleRepeatedMeshes = 0;
let eligibleRepeatedNodes = 0;
let unbatchedDrawCallSavings = 0;
for (const [meshIndex, count] of repeatedMeshes) {
if (count < 3) continue;
eligibleRepeatedMeshes += 1;
eligibleRepeatedNodes += count;
unbatchedDrawCallSavings += (count - 1) * primitiveCount(json, meshIndex);
}
const materials = json.materials ?? [];
return {
nodes: json.nodes?.length ?? 0,
meshNodes,
meshes: json.meshes?.length ?? 0,
materials: materials.length,
textures: json.textures?.length ?? 0,
doubleSidedMaterials: materials.filter((material) => material.doubleSided === true).length,
blendedMaterials: materials.filter((material) => material.alphaMode === "BLEND").length,
estimatedDrawCalls,
expandedPrimitiveInstances,
gpuInstanceBatches,
gpuInstances,
eligibleRepeatedMeshes,
eligibleRepeatedNodes,
unbatchedDrawCallSavings,
usesGpuInstancing: (json.extensionsUsed ?? []).includes(GPU_INSTANCING_EXTENSION),
};
}
export async function readGlbJson(file) {
const handle = await open(file, "r");
try {
const header = Buffer.alloc(20);
const { bytesRead } = await handle.read(header, 0, header.length, 0);
if (bytesRead !== header.length
|| header.readUInt32LE(0) !== GLB_MAGIC
|| header.readUInt32LE(4) !== 2
|| header.readUInt32LE(16) !== JSON_CHUNK_TYPE) {
throw new Error(`${file} is not a supported GLB 2.0 file.`);
}
const jsonLength = header.readUInt32LE(12);
const jsonBuffer = Buffer.alloc(jsonLength);
const jsonRead = await handle.read(jsonBuffer, 0, jsonLength, 20);
if (jsonRead.bytesRead !== jsonLength) throw new Error(`${file} has a truncated JSON chunk.`);
return JSON.parse(jsonBuffer.toString("utf8"));
} finally {
await handle.close();
}
}
export async function analyzeGlbFile(file) {
return analyzeGlbJson(await readGlbJson(file));
}
@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import test from "node:test";
import { analyzeGlbJson } from "./glb-performance.mjs";
test("counts repeated mesh placements as avoidable draw calls", () => {
const report = analyzeGlbJson({
meshes: [
{ primitives: [{}, {}] },
{ primitives: [{}] },
],
nodes: [
{ mesh: 0 },
{ mesh: 0 },
{ mesh: 0 },
{ mesh: 1 },
],
materials: [
{ doubleSided: true, alphaMode: "BLEND" },
{},
],
textures: [{}, {}],
});
assert.equal(report.estimatedDrawCalls, 7);
assert.equal(report.expandedPrimitiveInstances, 7);
assert.equal(report.eligibleRepeatedMeshes, 1);
assert.equal(report.eligibleRepeatedNodes, 3);
assert.equal(report.unbatchedDrawCallSavings, 4);
assert.equal(report.doubleSidedMaterials, 1);
assert.equal(report.blendedMaterials, 1);
});
test("counts one draw-call set for an EXT_mesh_gpu_instancing batch", () => {
const report = analyzeGlbJson({
extensionsUsed: ["EXT_mesh_gpu_instancing"],
accessors: [{ count: 12 }],
meshes: [{ primitives: [{}, {}, {}] }],
nodes: [{
mesh: 0,
extensions: {
EXT_mesh_gpu_instancing: {
attributes: { TRANSLATION: 0 },
},
},
}],
});
assert.equal(report.estimatedDrawCalls, 3);
assert.equal(report.expandedPrimitiveInstances, 36);
assert.equal(report.gpuInstanceBatches, 1);
assert.equal(report.gpuInstances, 12);
assert.equal(report.unbatchedDrawCallSavings, 0);
assert.equal(report.usesGpuInstancing, true);
});
+106
View File
@@ -0,0 +1,106 @@
import {
mkdirSync,
readFileSync,
readdirSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { basename, resolve } from "node:path";
import {
CLASSES,
COA_CLASS_IDS,
type ClassId,
type CoaClassId,
} from "../src/app/characterCatalog";
import { TALENT_NODES, TALENT_TREES } from "../src/game/talentCatalog";
const projectRoot = resolve(import.meta.dirname, "..");
const talentOutputDirectory = resolve(projectRoot, "src/game/generated/talentUi");
const coaAbilityOutputDirectory = resolve(projectRoot, "src/game/generated/coaAbilities");
function writeJson(file: string, value: unknown): void {
writeFileSync(file, `${JSON.stringify(value)}\n`, "utf8");
console.log(`Wrote ${file}`);
}
function removeStaleJson(directory: string, expectedNames: ReadonlySet<string>): void {
for (const name of readdirSync(directory)) {
if (!name.endsWith(".json") || expectedNames.has(name)) continue;
unlinkSync(resolve(directory, name));
}
}
mkdirSync(talentOutputDirectory, { recursive: true });
const talentClassIds = new Set<ClassId>(TALENT_TREES.map((tree) => tree.classId));
const talentFileNames = new Set([...talentClassIds].map((classId) => `${classId}.json`));
removeStaleJson(talentOutputDirectory, talentFileNames);
for (const classId of talentClassIds) {
const trees = TALENT_TREES.filter((tree) => tree.classId === classId).map((tree) => ({
id: tree.id,
classId: tree.classId,
name: tree.name,
icon: tree.icon,
background: tree.background,
description: tree.description,
system: tree.system,
}));
const treeIds = new Set(trees.map((tree) => tree.id));
const nodes = TALENT_NODES.filter((node) => treeIds.has(node.treeId)).map((node) => ({
id: node.id,
treeId: node.treeId,
index: node.index,
prerequisiteId: node.prerequisiteId,
prerequisiteRank: node.prerequisiteRank,
prerequisites: node.prerequisites,
column: node.column,
row: node.row,
icon: node.icon,
maxRank: node.maxRank,
name: node.name,
description: node.description,
rankDescriptions: node.rankDescriptions,
system: node.system,
entryType: node.entryType,
abilityEssenceCost: node.abilityEssenceCost,
talentEssenceCost: node.talentEssenceCost,
requiredClassPoints: node.requiredClassPoints,
requiredSpecPoints: node.requiredSpecPoints,
requiredLevel: node.requiredLevel,
isPassive: node.isPassive,
}));
writeJson(resolve(talentOutputDirectory, `${classId}.json`), {
schemaVersion: 1,
classId,
trees,
nodes,
});
}
interface CoaAbilitySnapshot {
readonly schemaVersion: 1;
readonly abilitiesByClass: Readonly<Record<CoaClassId, readonly unknown[]>>;
readonly resourcesByClass: Readonly<Record<CoaClassId, unknown>>;
readonly triggeredAbilitiesBySpellId: Readonly<Record<string, { readonly classId?: ClassId }>>;
}
const coaAbilityInput = resolve(projectRoot, "src/game/generated/coaAbilityRuntimeCatalog.json");
const coaSnapshot = JSON.parse(readFileSync(coaAbilityInput, "utf8")) as CoaAbilitySnapshot;
mkdirSync(coaAbilityOutputDirectory, { recursive: true });
const coaAbilityFileNames = new Set(COA_CLASS_IDS.map((classId) => `${classId}.json`));
removeStaleJson(coaAbilityOutputDirectory, coaAbilityFileNames);
for (const classId of COA_CLASS_IDS) {
const triggeredAbilitiesBySpellId = Object.fromEntries(Object.entries(
coaSnapshot.triggeredAbilitiesBySpellId,
).filter(([, ability]) => ability.classId === classId));
writeJson(resolve(coaAbilityOutputDirectory, `${classId}.json`), {
schemaVersion: 1,
classId,
abilities: coaSnapshot.abilitiesByClass[classId] ?? [],
resource: coaSnapshot.resourcesByClass[classId],
triggeredAbilitiesBySpellId,
});
}
console.log(`Generated ${talentFileNames.size} talent UI shards and ${coaAbilityFileNames.size} Conquest ability shards from ${basename(coaAbilityInput)}.`);
+25 -8
View File
@@ -404,15 +404,23 @@ async function optimizeStage(slug) {
const source = path.join(staging, entry.path);
const outputName = optimizedChunkName(role, index, entries.length);
const output = path.join(staging, outputName);
await run(process.execPath, [
cli,
"meshopt",
source,
output,
"--level",
"high",
]);
if (role === "visual") {
await run(process.execPath, [
cli,
"optimize",
source,
output,
"--compress",
"meshopt",
"--meshopt-level",
"high",
"--instance",
"true",
"--instance-min",
"3",
"--texture-size",
"2048",
]);
await run(process.execPath, [
path.join(projectRoot, "scripts/asset-pipeline/compress-ktx2.mjs"),
output,
@@ -421,6 +429,15 @@ async function optimizeStage(slug) {
"--jobs",
"2",
]);
} else {
await run(process.execPath, [
cli,
"meshopt",
source,
output,
"--level",
"high",
]);
}
const geometry = await inspectGlb(output);
const descriptor = {