Release Healer Man 0.1.24

This commit is contained in:
phenom committed 2026-09-09 16:43:37 -04:00
1 parent c4346751a9
commit bac0da9300
118 files changed
+857 -289

No files matched your search

+15
View File
@@ -1839,3 +1839,18 @@ Vite's existing large-chunk advisory remains informational.
no environment-performance regressions. The release-candidate check accepted no environment-performance regressions. The release-candidate check accepted
all 118 changed paths. The existing large-chunk advisory and Rapier all 118 changed paths. The existing large-chunk advisory and Rapier
initialization deprecation warning remain informational. initialization deprecation warning remain informational.
# 2026-09-09 — fixed per-rank classic mana costs
- Imported WoW mana percentages are now converted into fixed costs at each
spell rank's source level. Player level, Intellect, equipment, and maximum-mana
modifiers no longer increase the cost of an already learned rank; legitimate
talent and aura cost reductions are still applied afterward.
- Shadow Word: Pain rank 4 now costs 133 mana at every level from 26 through 33,
including level 32. Learning rank 5 at level 34 raises the cost to 188 mana.
- AI companions now use real character-scale mana pools so fixed spell costs do
not prevent healing or resurrection. The companion display also resolves the
active spell rank and the same final affordability result as the primary HUD.
- Verification passed all 1,133 Vitest cases across 208 files. The production
build completed with 974 modules transformed; arena, KTX2, and environment-
performance audits remained green, and the release-candidate check passed.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "healer-man", "name": "healer-man",
"version": "0.1.23", "version": "0.1.24",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "healer-man", "name": "healer-man",
"version": "0.1.23", "version": "0.1.24",
"dependencies": { "dependencies": {
"@capacitor/android": "8.4.1", "@capacitor/android": "8.4.1",
"@capacitor/core": "8.4.1", "@capacitor/core": "8.4.1",
+2 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "healer-man", "name": "healer-man",
"private": true, "private": true,
"version": "0.1.23", "version": "0.1.24",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 0.0.0.0", "dev": "vite --host 0.0.0.0",
@@ -95,6 +95,7 @@
"runewaker:audit:encounters": "node scripts/runewaker-pipeline/audit-encounter-implementation.mjs && node --test scripts/runewaker-pipeline/encounter-implementation-report.test.mjs", "runewaker:audit:encounters": "node scripts/runewaker-pipeline/audit-encounter-implementation.mjs && node --test scripts/runewaker-pipeline/encounter-implementation-report.test.mjs",
"runewaker:audit:environment-assembly": "node scripts/runewaker-pipeline/audit-environment-assembly.mjs && node --test scripts/runewaker-pipeline/environment-assembly.test.mjs", "runewaker:audit:environment-assembly": "node scripts/runewaker-pipeline/audit-environment-assembly.mjs && node --test scripts/runewaker-pipeline/environment-assembly.test.mjs",
"runewaker:audit:navigation": "node scripts/runewaker-pipeline/analyze-navigation-gaps.mjs && vitest run src/game/runewakerObjectiveRouteAudit.test.ts", "runewaker:audit:navigation": "node scripts/runewaker-pipeline/analyze-navigation-gaps.mjs && vitest run src/game/runewakerObjectiveRouteAudit.test.ts",
"runewaker:audit:material-alpha": "node scripts/runewaker-pipeline/audit-material-alpha.mjs",
"assets:wailing:instance": "gltf-transform instance ../wow335a/dungeon-export-work/WailingCaverns/converted/wailing-caverns.visual.glb src/assets/game/dungeons/wailing-caverns/wailing-caverns-visual.glb --min 3", "assets:wailing:instance": "gltf-transform instance ../wow335a/dungeon-export-work/WailingCaverns/converted/wailing-caverns.visual.glb src/assets/game/dungeons/wailing-caverns/wailing-caverns-visual.glb --min 3",
"assets:wailing:creatures:animated": "node scripts/prepareAnimatedCreatures.mjs", "assets:wailing:creatures:animated": "node scripts/prepareAnimatedCreatures.mjs",
"assets:wailing:creatures:combat": "node scripts/add-combat-creature-clips.mjs", "assets:wailing:creatures:combat": "node scripts/add-combat-creature-clips.mjs",
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"versionName": "0.1.23", "versionName": "0.1.24",
"versionCode": 1023 "versionCode": 1024
} }
@@ -36,6 +36,9 @@ async function main() {
&& hasBasisu && hasBasisu
&& /^\/images\/\d+\/mimeType$/.test(issue.pointer ?? "") && /^\/images\/\d+\/mimeType$/.test(issue.pointer ?? "")
&& issue.message.includes("'image/ktx2'")) return false; && issue.message.includes("'image/ktx2'")) return false;
if (issue.code === "IMAGE_UNRECOGNIZED_FORMAT"
&& hasBasisu
&& /^\/images\/\d+$/.test(issue.pointer ?? "")) return false;
return true; return true;
}); });
rows.push({ rows.push({
@@ -0,0 +1,262 @@
import { createHash } from "node:crypto";
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import sharp from "sharp";
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const recipeDirectory = path.join(projectRoot, "scripts", "runewaker-pipeline", "recipes");
const workRoot = path.join(projectRoot, "runewaker-export-work");
const shippingRoot = path.join(projectRoot, "src", "assets", "game", "dungeons");
const resourceCacheRoot = path.join(workRoot, "resource-cache");
const requestedSlugs = new Set(process.argv.slice(2).filter((value) => !value.startsWith("--")));
const fix = process.argv.includes("--fix");
const alphaClassifications = new Map();
let resourceTextureIndex;
async function exists(file) {
try {
await stat(file);
return true;
} catch {
return false;
}
}
function parseGlb(buffer, file) {
if (buffer.length < 20 || buffer.toString("utf8", 0, 4) !== "glTF") {
throw new Error(`${file} is not a GLB file.`);
}
const chunks = [];
let json;
let offset = 12;
while (offset + 8 <= buffer.length) {
const chunkLength = buffer.readUInt32LE(offset);
const chunkType = buffer.readUInt32LE(offset + 4);
const chunkStart = offset + 8;
const chunkEnd = chunkStart + chunkLength;
if (chunkEnd > buffer.length) break;
const data = buffer.subarray(chunkStart, chunkEnd);
chunks.push({ type: chunkType, data });
if (chunkType === 0x4e4f534a) {
json = JSON.parse(data.toString("utf8").replace(/\0+$/u, "").trim());
}
offset = chunkEnd;
}
if (!json) throw new Error(`${file} does not contain a JSON chunk.`);
return { json, chunks };
}
function serializeGlb(source, json, chunks) {
const encodedJson = Buffer.from(JSON.stringify(json));
const paddedJson = Buffer.alloc(Math.ceil(encodedJson.length / 4) * 4, 0x20);
encodedJson.copy(paddedJson);
const updatedChunks = chunks.map((chunk) => (
chunk.type === 0x4e4f534a ? { ...chunk, data: paddedJson } : chunk
));
const totalLength = 12 + updatedChunks.reduce((total, chunk) => total + 8 + chunk.data.length, 0);
const output = Buffer.alloc(totalLength);
source.copy(output, 0, 0, 12);
output.writeUInt32LE(totalLength, 8);
let offset = 12;
for (const chunk of updatedChunks) {
output.writeUInt32LE(chunk.data.length, offset);
output.writeUInt32LE(chunk.type, offset + 4);
chunk.data.copy(output, offset + 8);
offset += 8 + chunk.data.length;
}
return output;
}
async function classifyAlpha(file) {
if (alphaClassifications.has(file)) return alphaClassifications.get(file);
const metadata = await sharp(file).metadata();
if (!metadata.hasAlpha) {
alphaClassifications.set(file, "opaque");
return "opaque";
}
const { data, info } = await sharp(file).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
let hasTransparentPixel = false;
for (let offset = 3; offset < data.length; offset += info.channels) {
const alpha = data[offset];
if (alpha === 255) continue;
hasTransparentPixel = true;
if (alpha > 0) {
alphaClassifications.set(file, "blend");
return "blend";
}
}
const role = hasTransparentPixel ? "cutout" : "opaque";
alphaClassifications.set(file, role);
return role;
}
function sourceImageIndex(texture) {
return texture?.extensions?.KHR_texture_basisu?.source ?? texture?.source;
}
async function indexResourceTextures() {
if (resourceTextureIndex) return resourceTextureIndex;
resourceTextureIndex = new Map();
if (!(await exists(resourceCacheRoot))) return resourceTextureIndex;
const cacheDirectories = (await readdir(resourceCacheRoot, { withFileTypes: true }))
.filter((entry) => entry.isDirectory())
.sort((left, right) => left.name.localeCompare(right.name));
for (const cacheDirectory of cacheDirectories) {
const textureDirectory = path.join(resourceCacheRoot, cacheDirectory.name, "prepared", "textures");
if (!(await exists(textureDirectory))) continue;
for (const texture of (await readdir(textureDirectory)).filter((file) => file.endsWith(".png")).sort()) {
if (!resourceTextureIndex.has(texture)) {
resourceTextureIndex.set(texture, path.join(textureDirectory, texture));
}
}
}
return resourceTextureIndex;
}
async function resolveSourceTexture(localDirectory, imageName) {
const fileName = `${imageName}.png`;
const localFile = path.join(localDirectory, fileName);
if (await exists(localFile)) return localFile;
return (await indexResourceTextures()).get(fileName);
}
function sha256(buffer) {
return createHash("sha256").update(buffer).digest("hex");
}
async function refreshShippingMetadata(metadataFile, metadata, visualArtifact, visualBuffer) {
visualArtifact.size = visualBuffer.length;
visualArtifact.sha256 = sha256(visualBuffer);
await writeFile(metadataFile, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
const optimizationFile = path.join(path.dirname(metadataFile), "optimization-report.json");
if (!(await exists(optimizationFile))) return;
const optimization = JSON.parse(await readFile(optimizationFile, "utf8"));
if (optimization.optimized) {
optimization.optimized.size = visualBuffer.length;
if (Object.hasOwn(optimization.optimized, "checksum")) {
optimization.optimized.checksum = sha256(visualBuffer);
}
}
await writeFile(optimizationFile, `${JSON.stringify(optimization, null, 2)}\n`, "utf8");
}
async function auditDungeon(recipe) {
const slug = recipe.slug;
const metadataFile = path.join(shippingRoot, slug, "import-metadata.json");
const preparedTextureDirectory = path.join(workRoot, slug, "prepared", "textures");
if (!(await exists(metadataFile))) {
return { slug, status: "incomplete", problems: [`Missing ${path.relative(projectRoot, metadataFile)}`] };
}
if (!(await exists(preparedTextureDirectory))) {
return {
slug,
status: "incomplete",
problems: [`Missing ${path.relative(projectRoot, preparedTextureDirectory)}`],
};
}
const metadata = JSON.parse(await readFile(metadataFile, "utf8"));
const visualArtifact = (metadata.assets ?? metadata.artifacts ?? [])
.find((artifact) => artifact.role === "visual");
const visualFile = path.join(shippingRoot, slug, visualArtifact?.fileName ?? `${slug}-visual.glb`);
if (!(await exists(visualFile))) {
return { slug, status: "incomplete", problems: [`Missing ${path.relative(projectRoot, visualFile)}`] };
}
const sourceGlb = await readFile(visualFile);
const { json: gltf, chunks } = parseGlb(sourceGlb, visualFile);
const materialResults = [];
const missingTextures = [];
for (const [materialIndex, material] of (gltf.materials ?? []).entries()) {
const textureIndex = material.pbrMetallicRoughness?.baseColorTexture?.index;
const texture = gltf.textures?.[textureIndex];
const image = gltf.images?.[sourceImageIndex(texture)];
if (!image?.name) continue;
const sourceFile = await resolveSourceTexture(preparedTextureDirectory, image.name);
if (!sourceFile) {
missingTextures.push(`${image.name}.png`);
continue;
}
const expectedRole = await classifyAlpha(sourceFile);
const expectedAlphaMode = expectedRole === "blend" ? "BLEND" : expectedRole === "cutout" ? "MASK" : "OPAQUE";
const actualAlphaMode = material.alphaMode ?? "OPAQUE";
const actualRole = material.extras?.semanticRole ?? "missing";
materialResults.push({
materialIndex,
material: material.name ?? `material-${materialIndex}`,
texture: image.name,
expectedRole,
expectedAlphaMode,
actualRole,
actualAlphaMode,
matches: expectedAlphaMode === actualAlphaMode && expectedRole === actualRole,
});
if (fix && (expectedAlphaMode !== actualAlphaMode || expectedRole !== actualRole)) {
if (expectedAlphaMode === "OPAQUE") delete material.alphaMode;
else material.alphaMode = expectedAlphaMode;
if (expectedAlphaMode === "MASK") material.alphaCutoff = 0.5;
else delete material.alphaCutoff;
material.extras = { ...(material.extras ?? {}), semanticRole: expectedRole };
}
}
const mismatches = materialResults.filter((result) => !result.matches);
const alphaMaterials = materialResults.filter((result) => result.expectedRole !== "opaque");
const problems = [
...mismatches.map((result) =>
`${result.texture}: expected ${result.expectedAlphaMode}/${result.expectedRole}, got ${result.actualAlphaMode}/${result.actualRole}`,
),
...[...new Set(missingTextures)].map((file) => `Missing prepared source texture ${file}`),
];
if (fix && mismatches.length > 0 && missingTextures.length === 0) {
const visualBuffer = serializeGlb(sourceGlb, gltf, chunks);
await writeFile(visualFile, visualBuffer);
if (visualArtifact) {
await refreshShippingMetadata(metadataFile, metadata, visualArtifact, visualBuffer);
}
}
return {
slug,
status: problems.length === 0 ? "green" : fix && missingTextures.length === 0 ? "repaired" : "mismatch",
materials: materialResults.length,
alphaMaterials: alphaMaterials.map(({ material, texture, expectedAlphaMode }) => ({
material,
texture,
alphaMode: expectedAlphaMode,
})),
mismatches,
problems,
};
}
const recipeFiles = (await readdir(recipeDirectory))
.filter((file) => file.endsWith(".json") && !file.endsWith("-population.json"))
.sort();
const recipes = [];
for (const recipeFile of recipeFiles) {
const recipe = JSON.parse(await readFile(path.join(recipeDirectory, recipeFile), "utf8"));
if (!recipe.slug || recipe.slug === "runewaker-player-avatars") continue;
if (requestedSlugs.size > 0 && !requestedSlugs.has(recipe.slug)) continue;
recipes.push(recipe);
}
const results = [];
for (const recipe of recipes) {
results.push(await auditDungeon(recipe));
}
const failures = results.filter((result) => !["green", "repaired"].includes(result.status));
const report = {
schemaVersion: 1,
dungeonCount: results.length,
status: failures.length === 0 ? fix ? "repaired" : "green" : "mismatch",
failureCount: failures.length,
alphaDungeonCount: results.filter((result) => result.alphaMaterials?.length > 0).length,
results,
};
console.log(JSON.stringify(report, null, 2));
if (failures.length > 0) process.exitCode = 1;
@@ -6,6 +6,7 @@ Expected invocation:
""" """
from __future__ import annotations from __future__ import annotations
from array import array
import json import json
import math import math
import os import os
@@ -111,6 +112,69 @@ def material_image_key(material: bpy.types.Material) -> str:
return f"material:{material.name.casefold()}" return f"material:{material.name.casefold()}"
def material_image(material: bpy.types.Material) -> bpy.types.Image | None:
if not material.use_nodes or material.node_tree is None:
return None
for node in material.node_tree.nodes:
if node.type == "TEX_IMAGE" and node.image is not None:
return node.image
return None
def image_alpha_role(image: bpy.types.Image | None) -> str:
"""Classify source alpha without turning fully opaque RGBA textures translucent."""
if image is None or image.channels < 4:
return "opaque"
pixels = array("f", [0.0]) * len(image.pixels)
image.pixels.foreach_get(pixels)
alpha_values = pixels[3::image.channels]
if min(alpha_values, default=1.0) >= 0.999:
return "opaque"
if any(0.001 < alpha < 0.999 for alpha in alpha_values):
return "blend"
return "cutout"
def preserve_material_alpha(material: bpy.types.Material) -> str:
image = material_image(material)
role = image_alpha_role(image)
if role == "opaque" or material.node_tree is None:
return role
image_node = next(
node
for node in material.node_tree.nodes
if node.type == "TEX_IMAGE" and node.image == image
)
principled = next(
(node for node in material.node_tree.nodes if node.type == "BSDF_PRINCIPLED"),
None,
)
if principled is None:
return role
alpha_output = image_node.outputs.get("Alpha")
alpha_input = principled.inputs.get("Alpha")
if alpha_output is None or alpha_input is None:
return role
if role == "blend":
if not alpha_input.is_linked:
material.node_tree.links.new(alpha_output, alpha_input)
material.blend_method = "BLEND"
if hasattr(material, "surface_render_method"):
material.surface_render_method = "BLENDED"
else:
alpha_clip = material.node_tree.nodes.new("ShaderNodeMath")
alpha_clip.name = "RuneWaker Alpha Clip"
alpha_clip.operation = "GREATER_THAN"
alpha_clip.inputs[1].default_value = 0.5
material.node_tree.links.new(alpha_output, alpha_clip.inputs[0])
material.node_tree.links.new(alpha_clip.outputs[0], alpha_input)
material.blend_method = "CLIP"
material.alpha_threshold = 0.5
return role
def deduplicate_materials(objects: list[bpy.types.Object]) -> dict[str, int]: def deduplicate_materials(objects: list[bpy.types.Object]) -> dict[str, int]:
canonical: dict[str, bpy.types.Material] = {} canonical: dict[str, bpy.types.Material] = {}
replacements = 0 replacements = 0
@@ -126,7 +190,7 @@ def deduplicate_materials(objects: list[bpy.types.Object]) -> dict[str, int]:
replacements += 1 replacements += 1
else: else:
canonical[key] = material canonical[key] = material
material["semanticRole"] = "opaque" material["semanticRole"] = preserve_material_alpha(material)
material["sourceFormat"] = "RuneWaker DDS" material["sourceFormat"] = "RuneWaker DDS"
return {"unique": len(canonical), "replacements": replacements} return {"unique": len(canonical), "replacements": replacements}
@@ -241,6 +241,7 @@ async function cachedResourceAsset(resourceRecord) {
throw new Error("source ROS is missing: " + projectPath(sourceFile)); throw new Error("source ROS is missing: " + projectPath(sourceFile));
} }
const sourceSha256 = await sha256(sourceFile); const sourceSha256 = await sha256(sourceFile);
const converterSha256 = await sha256(blenderScript);
const cacheRoot = path.join(resourceCacheDirectory, resourceRecord.cacheKey); const cacheRoot = path.join(resourceCacheDirectory, resourceRecord.cacheKey);
const raw = path.join(cacheRoot, "raw"); const raw = path.join(cacheRoot, "raw");
const prepared = path.join(cacheRoot, "prepared"); const prepared = path.join(cacheRoot, "prepared");
@@ -253,6 +254,7 @@ async function cachedResourceAsset(resourceRecord) {
.map((file) => path.join(cacheRoot, file)); .map((file) => path.join(cacheRoot, file));
if (cached.source?.resource === resourceRecord.normalizedResource if (cached.source?.resource === resourceRecord.normalizedResource
&& cached.source?.sha256 === sourceSha256 && cached.source?.sha256 === sourceSha256
&& cached.toolchain?.converterSha256 === converterSha256
&& cached.coordinateSystem?.metersPerSourceUnit === recipe.coordinateSystem.metersPerSourceUnit && cached.coordinateSystem?.metersPerSourceUnit === recipe.coordinateSystem.metersPerSourceUnit
&& (await Promise.all(outputs.map(exists))).every(Boolean)) { && (await Promise.all(outputs.map(exists))).every(Boolean)) {
return { return {
@@ -300,6 +302,9 @@ async function cachedResourceAsset(resourceRecord) {
nativeBridge: recipe.coordinateSystem.nativeBridge, nativeBridge: recipe.coordinateSystem.nativeBridge,
metersPerSourceUnit: recipe.coordinateSystem.metersPerSourceUnit, metersPerSourceUnit: recipe.coordinateSystem.metersPerSourceUnit,
}, },
toolchain: {
converterSha256,
},
visual: { visual: {
file: path.relative( file: path.relative(
cacheRoot, cacheRoot,
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "aeternal-circle-visual.glb", "fileName": "aeternal-circle-visual.glb",
"size": 4963912, "size": 4963968,
"sha256": "42fdcff1b25b58688943a030f69fd14ea839247a7baabbc31b5f6f7faaf49785", "sha256": "de1ba06b7e3a9237fcff75aff2b00093287975e755cc7e98e77de1b7af6a9c14",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "aeternal-circle-visual.optimized.glb", "file": "aeternal-circle-visual.optimized.glb",
"size": 4963912, "size": 4963968,
"checksum": "42fdcff1b25b58688943a030f69fd14ea839247a7baabbc31b5f6f7faaf49785" "checksum": "de1ba06b7e3a9237fcff75aff2b00093287975e755cc7e98e77de1b7af6a9c14"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "barren-caves-visual.glb", "fileName": "barren-caves-visual.glb",
"size": 1710152, "size": 1710188,
"sha256": "d0f733de4b26c0f432c5e160614765f320bf1753a829be269740b340e7415a12", "sha256": "e6dc49e6aa6fd20cee1b2afa43324a714f8f8284a6becef8dc514509b432f9bb",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "barren-caves-visual.optimized.glb", "file": "barren-caves-visual.optimized.glb",
"size": 1710152, "size": 1710188,
"checksum": "d0f733de4b26c0f432c5e160614765f320bf1753a829be269740b340e7415a12" "checksum": "e6dc49e6aa6fd20cee1b2afa43324a714f8f8284a6becef8dc514509b432f9bb"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "cave-water-dragon-visual.glb", "fileName": "cave-water-dragon-visual.glb",
"size": 8893112, "size": 8893204,
"sha256": "cc57c0285de77690c4a1cdabaecd8b192791cbd05aca38aa21ed96462688ca31", "sha256": "dd1887f88d6ee9102da0e34ea2b4087742e37a2b8ce779c722bc124effe1faca",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "cave-water-dragon-visual.optimized.glb", "file": "cave-water-dragon-visual.optimized.glb",
"size": 8893112, "size": 8893204,
"checksum": "cc57c0285de77690c4a1cdabaecd8b192791cbd05aca38aa21ed96462688ca31" "checksum": "dd1887f88d6ee9102da0e34ea2b4087742e37a2b8ce779c722bc124effe1faca"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "demon-stronghold-125-visual.glb", "fileName": "demon-stronghold-125-visual.glb",
"size": 15134500, "size": 15135060,
"sha256": "ecbacfe274e7eecb104c5807eedf3cc51cd4b7a628967c6331fc93b86396b842", "sha256": "e2106115dcc216999d2e37d26eeb52844ce4f41b44e19594699f37a54d7f19b9",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "demon-stronghold-125-visual.optimized.glb", "file": "demon-stronghold-125-visual.optimized.glb",
"size": 15134500, "size": 15135060,
"checksum": "ecbacfe274e7eecb104c5807eedf3cc51cd4b7a628967c6331fc93b86396b842" "checksum": "e2106115dcc216999d2e37d26eeb52844ce4f41b44e19594699f37a54d7f19b9"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "demon-stronghold-visual.glb", "fileName": "demon-stronghold-visual.glb",
"size": 15166984, "size": 15167544,
"sha256": "9368984b3c183a8db6e8b7e18b7818edcf41b8edd45ffeca1d1df28aadc592a2", "sha256": "664181dc790b9e68cdc7451dbd0b1170a033a71ca19375743b6e66ca25889723",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "demon-stronghold-visual.optimized.glb", "file": "demon-stronghold-visual.optimized.glb",
"size": 15166984, "size": 15167544,
"checksum": "9368984b3c183a8db6e8b7e18b7818edcf41b8edd45ffeca1d1df28aadc592a2" "checksum": "664181dc790b9e68cdc7451dbd0b1170a033a71ca19375743b6e66ca25889723"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "dungeon-of-dalanis-visual.glb", "fileName": "dungeon-of-dalanis-visual.glb",
"size": 11936864, "size": 11937088,
"sha256": "4c584ef34779e516f8e6ff3a4907eaab1c7c8d49d4c302de66e17b09222d8829", "sha256": "45cad819bc348dec8926f7de237c625e9d18697b4200804e0440dd16b8976959",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "dungeon-of-dalanis-visual.optimized.glb", "file": "dungeon-of-dalanis-visual.optimized.glb",
"size": 11936864, "size": 11937088,
"checksum": "4c584ef34779e516f8e6ff3a4907eaab1c7c8d49d4c302de66e17b09222d8829" "checksum": "45cad819bc348dec8926f7de237c625e9d18697b4200804e0440dd16b8976959"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -284,8 +284,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "forsaken-abbey-visual.glb", "fileName": "forsaken-abbey-visual.glb",
"size": 9580212, "size": 9580568,
"sha256": "37800c26cdf784658a57dcc7328ab1b9b23e46d018be6d1e6420f127768b0332", "sha256": "ee7afd5e9179e6bcb7ec9171798466282e09d4ef696a3f9639bcd2bbc2ae8f48",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "forsaken-abbey-visual.optimized.glb", "file": "forsaken-abbey-visual.optimized.glb",
"size": 9580212, "size": 9580568,
"checksum": "37800c26cdf784658a57dcc7328ab1b9b23e46d018be6d1e6420f127768b0332" "checksum": "ee7afd5e9179e6bcb7ec9171798466282e09d4ef696a3f9639bcd2bbc2ae8f48"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "grafu-castle-visual.glb", "fileName": "grafu-castle-visual.glb",
"size": 11149716, "size": 11150124,
"sha256": "ba4552484ef9cddc1c00607d0dfd4c8fb0bae5b3cd3f992d215a580dac1e96ed", "sha256": "b775778f457d639e3f21982aaa6bd3ee1b0eba951c711d1ba319b0588e24a38f",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "grafu-castle-visual.optimized.glb", "file": "grafu-castle-visual.optimized.glb",
"size": 11149716, "size": 11150124,
"checksum": "ba4552484ef9cddc1c00607d0dfd4c8fb0bae5b3cd3f992d215a580dac1e96ed" "checksum": "b775778f457d639e3f21982aaa6bd3ee1b0eba951c711d1ba319b0588e24a38f"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "hall-of-survivors-visual.glb", "fileName": "hall-of-survivors-visual.glb",
"size": 15216768, "size": 15216936,
"sha256": "18b9d116da599159719ad76eeee45a4d89056ee8c61cc4f21cab9a1d9228fff7", "sha256": "96b91b24462b8428e8808295dcea29d26de9be8431ea6667dde92cdded4ddf2a",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "hall-of-survivors-visual.optimized.glb", "file": "hall-of-survivors-visual.optimized.glb",
"size": 15216768, "size": 15216936,
"checksum": "18b9d116da599159719ad76eeee45a4d89056ee8c61cc4f21cab9a1d9228fff7" "checksum": "96b91b24462b8428e8808295dcea29d26de9be8431ea6667dde92cdded4ddf2a"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.
@@ -3,7 +3,7 @@
"dungeonId": "hoto", "dungeonId": "hoto",
"title": "HOTO", "title": "HOTO",
"status": "source-packaged", "status": "source-packaged",
"generatedAt": "2026-08-01T01:00:34.655Z", "generatedAt": "2026-09-09T19:39:49.407Z",
"source": { "source": {
"recipe": "scripts/runewaker-pipeline/recipes/hoto.json", "recipe": "scripts/runewaker-pipeline/recipes/hoto.json",
"wdb": "Resource/wdb/dungeon/dgn_echoes_of_the_sea.wdb", "wdb": "Resource/wdb/dungeon/dgn_echoes_of_the_sea.wdb",
@@ -261,33 +261,37 @@
{ {
"role": "visual", "role": "visual",
"fileName": "hoto-visual.glb", "fileName": "hoto-visual.glb",
"size": 6667208, "relativeFile": "hoto-visual.glb",
"sha256": "0d12b9c6dc477704d2833c81f76113d340da7f07f5885a40bafd1321524e455e", "size": 6710184,
"compression": "meshopt+ktx2" "sha256": "beff3a180c55166d5ebd7e3610e65b59f8104edfd7596aa22ac3a49c578fcdc0"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "hoto-collision-000.glb", "fileName": "hoto-collision-000.glb",
"relativeFile": "hoto-collision-000.glb",
"size": 3088036, "size": 3088036,
"sha256": "3116529f2d262ff4cab7997e852f9d15caacab6298921360b49dcafd52b8c475" "sha256": "3116529f2d262ff4cab7997e852f9d15caacab6298921360b49dcafd52b8c475"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "hoto-collision-001.glb", "fileName": "hoto-collision-001.glb",
"relativeFile": "hoto-collision-001.glb",
"size": 2989324, "size": 2989324,
"sha256": "1f5cbd4b04b5a060029fd7c023ee19db5d8646566fcd01b931473b5b13c13b9f" "sha256": "1f5cbd4b04b5a060029fd7c023ee19db5d8646566fcd01b931473b5b13c13b9f"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "hoto-collision-002.glb", "fileName": "hoto-collision-002.glb",
"relativeFile": "hoto-collision-002.glb",
"size": 526648, "size": 526648,
"sha256": "0a468a6ef30941c04a20456866f0593f7e1f9f9bf8dc0024176d2128975f1879" "sha256": "0a468a6ef30941c04a20456866f0593f7e1f9f9bf8dc0024176d2128975f1879"
}, },
{ {
"role": "navigation", "role": "navigation",
"fileName": "hoto-navigation.glb", "fileName": "hoto-navigation.glb",
"relativeFile": "hoto-navigation.glb",
"size": 482260, "size": 482260,
"sha256": "2cd78e6d8f52cb6488747a1f87cb688eedb77ccf412fb50674c25f70306477fd" "sha256": "a2bde2e4b8ed0d0bac8c4669a05192ff0a35ae9c64df0c0a29fe3735b65cbf4b"
} }
], ],
"review": { "review": {
@@ -300,6 +304,6 @@
"exceptions": [ "exceptions": [
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.", "Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
"Collision initially includes decorative primary-ROS geometry.", "Collision initially includes decorative primary-ROS geometry.",
"Original skeletal animations and complex scripted mechanics remain later passes." "Original GR2 actor animations are recovered where source graphs and validation permit; scripted encounter mechanics remain evidence-driven per-instance work."
] ]
} }
@@ -4,15 +4,11 @@
"status": "review-required", "status": "review-required",
"source": { "source": {
"file": "hoto-visual.glb", "file": "hoto-visual.glb",
"size": 26313948 "size": 26314004
}, },
"optimized": { "optimized": {
"file": "hoto-visual.optimized.glb", "file": "hoto-visual.optimized.glb",
"size": 6667208, "size": 6710184
"checksum": "0d12b9c6dc477704d2833c81f76113d340da7f07f5885a40bafd1321524e455e"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2"
"commands": [
"node scripts/asset-pipeline/compress-ktx2.mjs <optimized.glb> --concurrency 1 --jobs 2"
]
} }
@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"sourceWdb": "D:\\Projects\\rom-pvt-server-backups\\Runewaker\\Resource\\wdb\\dungeon\\dgn_echoes_of_the_sea.wdb", "sourceWdb": "F:\\Projects\\rom-pvt-server-backups\\Runewaker\\Resource\\wdb\\dungeon\\dgn_echoes_of_the_sea.wdb",
"coordinateSystem": "runewaker-left-handed-y-up", "coordinateSystem": "runewaker-left-handed-y-up",
"descriptorCount": 1627, "descriptorCount": 1627,
"descriptors": [ "descriptors": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "ice-dwarf-kingdom-visual.glb", "fileName": "ice-dwarf-kingdom-visual.glb",
"size": 9880192, "size": 9880304,
"sha256": "a6b902e85bb6026f18281d77efa235ec880ffc2fc13ea6bfdc431d24af768e00", "sha256": "41cef8ce1bf6dbf7df0c4e2797a325e4d50a3870d435d46684600894d4aa92e7",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "ice-dwarf-kingdom-visual.optimized.glb", "file": "ice-dwarf-kingdom-visual.optimized.glb",
"size": 9880192, "size": 9880304,
"checksum": "a6b902e85bb6026f18281d77efa235ec880ffc2fc13ea6bfdc431d24af768e00" "checksum": "41cef8ce1bf6dbf7df0c4e2797a325e4d50a3870d435d46684600894d4aa92e7"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "kal-turok-hive-visual.glb", "fileName": "kal-turok-hive-visual.glb",
"size": 1777616, "size": 1777728,
"sha256": "012867deefc4dc5a18c3e1a507309aa48f119cf4c2e88968fdd2787936ed7c24", "sha256": "75c0c596218cec07003e2def1d74c8e1f5ca3010ed370db558a1786b003f4f1c",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "kal-turok-hive-visual.optimized.glb", "file": "kal-turok-hive-visual.optimized.glb",
"size": 1777616, "size": 1777728,
"checksum": "012867deefc4dc5a18c3e1a507309aa48f119cf4c2e88968fdd2787936ed7c24" "checksum": "75c0c596218cec07003e2def1d74c8e1f5ca3010ed370db558a1786b003f4f1c"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -3,7 +3,7 @@
"dungeonId": "kalins-shrine", "dungeonId": "kalins-shrine",
"title": "Kalin's Shrine", "title": "Kalin's Shrine",
"status": "source-packaged", "status": "source-packaged",
"generatedAt": "2026-08-01T00:57:43.432Z", "generatedAt": "2026-09-09T18:51:01.674Z",
"source": { "source": {
"recipe": "scripts/runewaker-pipeline/recipes/kalins-shrine.json", "recipe": "scripts/runewaker-pipeline/recipes/kalins-shrine.json",
"wdb": "Resource/wdb/dungeon/dgn_kleeon_temple.wdb", "wdb": "Resource/wdb/dungeon/dgn_kleeon_temple.wdb",
@@ -277,33 +277,37 @@
{ {
"role": "visual", "role": "visual",
"fileName": "kalins-shrine-visual.glb", "fileName": "kalins-shrine-visual.glb",
"size": 8747072, "relativeFile": "kalins-shrine-visual.glb",
"sha256": "769accb48e23c9f80e28697e3e8454ef4148243a69ce247ed06ba4eb35bb0da7", "size": 8851724,
"compression": "meshopt+ktx2" "sha256": "023c43a56253e97fa0526185c61f76e0e9fa8398b7a35771728566c331cd22a9"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "kalins-shrine-collision-000.glb", "fileName": "kalins-shrine-collision-000.glb",
"relativeFile": "kalins-shrine-collision-000.glb",
"size": 4748976, "size": 4748976,
"sha256": "ed1e713c159e024b4786ef8befadbfedd3849ba580e7f49970e2785b6f4fff7d" "sha256": "ed1e713c159e024b4786ef8befadbfedd3849ba580e7f49970e2785b6f4fff7d"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "kalins-shrine-collision-001.glb", "fileName": "kalins-shrine-collision-001.glb",
"relativeFile": "kalins-shrine-collision-001.glb",
"size": 4555800, "size": 4555800,
"sha256": "5fea14212353593c8daced2e303f88dae010f8f73aedb9890a3eee67eb8dd7cd" "sha256": "5fea14212353593c8daced2e303f88dae010f8f73aedb9890a3eee67eb8dd7cd"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "kalins-shrine-collision-002.glb", "fileName": "kalins-shrine-collision-002.glb",
"relativeFile": "kalins-shrine-collision-002.glb",
"size": 1215728, "size": 1215728,
"sha256": "e51d48d1f4521f1a4722fd4c528a90bdfd2978a87d2ace0dd662f23475490565" "sha256": "e51d48d1f4521f1a4722fd4c528a90bdfd2978a87d2ace0dd662f23475490565"
}, },
{ {
"role": "navigation", "role": "navigation",
"fileName": "kalins-shrine-navigation.glb", "fileName": "kalins-shrine-navigation.glb",
"relativeFile": "kalins-shrine-navigation.glb",
"size": 327792, "size": 327792,
"sha256": "37fd7f8efac36f83279efbfb33df37646453af214db29597bd14c8e9f19ddc04" "sha256": "6b77496f7b46890d401f955f9254ae37d25d9a1287a3bc9e787e9a254f9d9071"
} }
], ],
"review": { "review": {
@@ -316,6 +320,6 @@
"exceptions": [ "exceptions": [
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.", "Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
"Collision initially includes decorative primary-ROS geometry.", "Collision initially includes decorative primary-ROS geometry.",
"Original skeletal animations and complex scripted mechanics remain later passes." "Original GR2 actor animations are recovered where source graphs and validation permit; scripted encounter mechanics remain evidence-driven per-instance work."
] ]
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 KiB

After

Width:  |  Height:  |  Size: 892 KiB

@@ -4,15 +4,11 @@
"status": "review-required", "status": "review-required",
"source": { "source": {
"file": "kalins-shrine-visual.glb", "file": "kalins-shrine-visual.glb",
"size": 26551164 "size": 26551296
}, },
"optimized": { "optimized": {
"file": "kalins-shrine-visual.optimized.glb", "file": "kalins-shrine-visual.optimized.glb",
"size": 8747072, "size": 8851724
"checksum": "769accb48e23c9f80e28697e3e8454ef4148243a69ce247ed06ba4eb35bb0da7"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2"
"commands": [
"node scripts/asset-pipeline/compress-ktx2.mjs <optimized.glb> --concurrency 1 --jobs 2"
]
} }
@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"sourceWdb": "D:\\Projects\\rom-pvt-server-backups\\Runewaker\\Resource\\wdb\\dungeon\\dgn_kleeon_temple.wdb", "sourceWdb": "F:\\Projects\\rom-pvt-server-backups\\Runewaker\\Resource\\wdb\\dungeon\\dgn_kleeon_temple.wdb",
"coordinateSystem": "runewaker-left-handed-y-up", "coordinateSystem": "runewaker-left-handed-y-up",
"descriptorCount": 943, "descriptorCount": 943,
"descriptors": [ "descriptors": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "kawaks-tomb-visual.glb", "fileName": "kawaks-tomb-visual.glb",
"size": 8963188, "size": 8963376,
"sha256": "de54d223725e2c3dfb0c47a97c208a212ef9423860bd0ac390f1482b302d3a32", "sha256": "3f1755780540c54a1ba9b528e646ebd39b12ab127e41fa2d497fd7749d202018",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "kawaks-tomb-visual.optimized.glb", "file": "kawaks-tomb-visual.optimized.glb",
"size": 8963188, "size": 8963376,
"checksum": "de54d223725e2c3dfb0c47a97c208a212ef9423860bd0ac390f1482b302d3a32" "checksum": "3f1755780540c54a1ba9b528e646ebd39b12ab127e41fa2d497fd7749d202018"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "kulech-bones-nest-visual.glb", "fileName": "kulech-bones-nest-visual.glb",
"size": 12146892, "size": 12147008,
"sha256": "2d912dcf7c0c8e59e36362fd947df1d5554c5dd5060590e1abb112e4c39f606f", "sha256": "b06b42b3c7f19abf9d645eec6be78f868cf6966e0b472e2ca4a1c36d3cf4d2d1",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "kulech-bones-nest-visual.optimized.glb", "file": "kulech-bones-nest-visual.optimized.glb",
"size": 12146892, "size": 12147008,
"checksum": "2d912dcf7c0c8e59e36362fd947df1d5554c5dd5060590e1abb112e4c39f606f" "checksum": "b06b42b3c7f19abf9d645eec6be78f868cf6966e0b472e2ca4a1c36d3cf4d2d1"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "lair-demon-dragon-visual.glb", "fileName": "lair-demon-dragon-visual.glb",
"size": 4551300, "size": 4551356,
"sha256": "d28481504802203e800985836f2b0907d67f6d6d0495c67500914e749d771ffe", "sha256": "b371465e96ef978915c9a515846ecf9dccb98607fd549617a1c71f45468fa5a7",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "lair-demon-dragon-visual.optimized.glb", "file": "lair-demon-dragon-visual.optimized.glb",
"size": 4551300, "size": 4551356,
"checksum": "d28481504802203e800985836f2b0907d67f6d6d0495c67500914e749d771ffe" "checksum": "b371465e96ef978915c9a515846ecf9dccb98607fd549617a1c71f45468fa5a7"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "mystic-altar-visual.glb", "fileName": "mystic-altar-visual.glb",
"size": 5552872, "size": 5552892,
"sha256": "765dd1b15bbe79fe1ffc2104e10caece19acaf86c919f0327419cb3609dbf600", "sha256": "c37f27643598c8fe6401828be2950bc08f299dfe491df37736fda4662c70a96e",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "mystic-altar-visual.optimized.glb", "file": "mystic-altar-visual.optimized.glb",
"size": 5552872, "size": 5552892,
"checksum": "765dd1b15bbe79fe1ffc2104e10caece19acaf86c919f0327419cb3609dbf600" "checksum": "c37f27643598c8fe6401828be2950bc08f299dfe491df37736fda4662c70a96e"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -3,7 +3,7 @@
"dungeonId": "paspers-shrine", "dungeonId": "paspers-shrine",
"title": "Pasper's Shrine", "title": "Pasper's Shrine",
"status": "source-packaged", "status": "source-packaged",
"generatedAt": "2026-08-01T01:00:57.399Z", "generatedAt": "2026-09-09T19:19:45.719Z",
"source": { "source": {
"recipe": "scripts/runewaker-pipeline/recipes/paspers-shrine.json", "recipe": "scripts/runewaker-pipeline/recipes/paspers-shrine.json",
"wdb": "Resource/wdb/dungeon/dgn_borsburs.wdb", "wdb": "Resource/wdb/dungeon/dgn_borsburs.wdb",
@@ -137,27 +137,30 @@
{ {
"role": "visual", "role": "visual",
"fileName": "paspers-shrine-visual.glb", "fileName": "paspers-shrine-visual.glb",
"size": 5625952, "relativeFile": "paspers-shrine-visual.glb",
"sha256": "82ea6cafd19c0a323c994795699223a07193807da29b300eb12dac41146f1034", "size": 5674908,
"compression": "meshopt+ktx2" "sha256": "bcf6443e8adb8b7f7dd81f4a97b79aac1c99756a83289bbada31853d2d9adb7c"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "paspers-shrine-collision-000.glb", "fileName": "paspers-shrine-collision-000.glb",
"relativeFile": "paspers-shrine-collision-000.glb",
"size": 4291972, "size": 4291972,
"sha256": "b6efbe1b90545dd2c2af3bfc7396616147211f6f79b999ccf81ca3680fdc984e" "sha256": "b6efbe1b90545dd2c2af3bfc7396616147211f6f79b999ccf81ca3680fdc984e"
}, },
{ {
"role": "collision", "role": "collision",
"fileName": "paspers-shrine-collision-001.glb", "fileName": "paspers-shrine-collision-001.glb",
"relativeFile": "paspers-shrine-collision-001.glb",
"size": 4479896, "size": 4479896,
"sha256": "1c6f52d7e21171acfbe3fb43335b2c7f3de13217881840947f15fd0722adb4de" "sha256": "1c6f52d7e21171acfbe3fb43335b2c7f3de13217881840947f15fd0722adb4de"
}, },
{ {
"role": "navigation", "role": "navigation",
"fileName": "paspers-shrine-navigation.glb", "fileName": "paspers-shrine-navigation.glb",
"relativeFile": "paspers-shrine-navigation.glb",
"size": 152356, "size": 152356,
"sha256": "e05fb0b7a3c86bba3ac46606365169dea6c575281b2bb15707addcedb279f108" "sha256": "4ebba3fb97a99df49c7301dbfcdb8154634cf4e56a85c5ed587c7515221d0a10"
} }
], ],
"review": { "review": {
@@ -170,6 +173,6 @@
"exceptions": [ "exceptions": [
"Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.", "Secondary WDB doodads/effects remain in the audit; this pass exports the primary ROS hierarchy.",
"Collision initially includes decorative primary-ROS geometry.", "Collision initially includes decorative primary-ROS geometry.",
"Original skeletal animations and complex scripted mechanics remain later passes." "Original GR2 actor animations are recovered where source graphs and validation permit; scripted encounter mechanics remain evidence-driven per-instance work."
] ]
} }
@@ -4,15 +4,11 @@
"status": "review-required", "status": "review-required",
"source": { "source": {
"file": "paspers-shrine-visual.glb", "file": "paspers-shrine-visual.glb",
"size": 14723576 "size": 14723652
}, },
"optimized": { "optimized": {
"file": "paspers-shrine-visual.optimized.glb", "file": "paspers-shrine-visual.optimized.glb",
"size": 5625952, "size": 5674908
"checksum": "82ea6cafd19c0a323c994795699223a07193807da29b300eb12dac41146f1034"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2"
"commands": [
"node scripts/asset-pipeline/compress-ktx2.mjs <optimized.glb> --concurrency 1 --jobs 2"
]
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1024 KiB

After

Width:  |  Height:  |  Size: 1024 KiB

@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"sourceWdb": "D:\\Projects\\rom-pvt-server-backups\\Runewaker\\Resource\\wdb\\dungeon\\dgn_borsburs.wdb", "sourceWdb": "F:\\Projects\\rom-pvt-server-backups\\Runewaker\\Resource\\wdb\\dungeon\\dgn_borsburs.wdb",
"coordinateSystem": "runewaker-left-handed-y-up", "coordinateSystem": "runewaker-left-handed-y-up",
"descriptorCount": 564, "descriptorCount": 564,
"descriptors": [ "descriptors": [
@@ -149,8 +149,8 @@
"role": "visual", "role": "visual",
"fileName": "raksha-temple-visual.glb", "fileName": "raksha-temple-visual.glb",
"relativeFile": "raksha-temple-visual.glb", "relativeFile": "raksha-temple-visual.glb",
"size": 23554528, "size": 23556584,
"sha256": "3a61098e772a0c656aa25d88aee357f78b84c738e45150fca6fd5059c2b675f7" "sha256": "1fad5149acfce870be822f5355369f15dd76fba280bbeda31ff7f371c442082f"
}, },
{ {
"role": "collision", "role": "collision",
@@ -8,7 +8,7 @@
}, },
"optimized": { "optimized": {
"file": "raksha-temple-visual.optimized.glb", "file": "raksha-temple-visual.optimized.glb",
"size": 23554528 "size": 23556584
}, },
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
} }
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "sardo-castle-visual.glb", "fileName": "sardo-castle-visual.glb",
"size": 7756568, "size": 7756828,
"sha256": "16d759af9f8a3f98281e89362c7fb52dceff6543bbd837a7c6f89015920cdd94", "sha256": "191396625e301a41afab38e168848a198a9be5918a071dadda841683a7c6261e",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "sardo-castle-visual.optimized.glb", "file": "sardo-castle-visual.optimized.glb",
"size": 7756568, "size": 7756828,
"checksum": "16d759af9f8a3f98281e89362c7fb52dceff6543bbd837a7c6f89015920cdd94" "checksum": "191396625e301a41afab38e168848a198a9be5918a071dadda841683a7c6261e"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -149,8 +149,8 @@
"role": "visual", "role": "visual",
"fileName": "the-origin-visual.glb", "fileName": "the-origin-visual.glb",
"relativeFile": "the-origin-visual.glb", "relativeFile": "the-origin-visual.glb",
"size": 28683180, "size": 28684736,
"sha256": "d1ded0316b2cbaa07458c56864c47c01afaa85e4f33dd13f5d95984d7305fb92" "sha256": "1f96c2b57a24470a003834a8c91cb0ceda5b01824d5bfa31904afd247c404e88"
}, },
{ {
"role": "collision", "role": "collision",
@@ -8,7 +8,7 @@
}, },
"optimized": { "optimized": {
"file": "the-origin-visual.optimized.glb", "file": "the-origin-visual.optimized.glb",
"size": 28683180 "size": 28684736
}, },
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
} }
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "tomb-seven-heroes-visual.glb", "fileName": "tomb-seven-heroes-visual.glb",
"size": 10910804, "size": 10911104,
"sha256": "d9f39da6b61c9f841c5d075e4985bc67e533ce422313fdf38d5f6c99dbb304c1", "sha256": "09250a1fbabf8a03f922c9a3950aee3daa1edc54215cd03ddc9bf5c96e1429bd",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "tomb-seven-heroes-visual.optimized.glb", "file": "tomb-seven-heroes-visual.optimized.glb",
"size": 10910804, "size": 10911104,
"checksum": "d9f39da6b61c9f841c5d075e4985bc67e533ce422313fdf38d5f6c99dbb304c1" "checksum": "09250a1fbabf8a03f922c9a3950aee3daa1edc54215cd03ddc9bf5c96e1429bd"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "treasure-trove-visual.glb", "fileName": "treasure-trove-visual.glb",
"size": 7433280, "size": 7433780,
"sha256": "f4c4dc12df65f8afc3c696308db17cb42b6a5894086dbe870dae2e21e1258448", "sha256": "c56d429187fd2ffbe20e11d5e7bbfd2128a779c9251fa2ab4b12286caf0de0b6",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "treasure-trove-visual.optimized.glb", "file": "treasure-trove-visual.optimized.glb",
"size": 7433280, "size": 7433780,
"checksum": "f4c4dc12df65f8afc3c696308db17cb42b6a5894086dbe870dae2e21e1258448" "checksum": "c56d429187fd2ffbe20e11d5e7bbfd2128a779c9251fa2ab4b12286caf0de0b6"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "warnorken-arena-visual.glb", "fileName": "warnorken-arena-visual.glb",
"size": 3752136, "size": 3752248,
"sha256": "c452a843c2e4123d4fe0d20da41558f5b5a1906548b3d091dc691ffc42ed19d9", "sha256": "ee81c9e7c94d624977143297ca6c070c40799fdda1c51b92044c70dff53987ea",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "warnorken-arena-visual.optimized.glb", "file": "warnorken-arena-visual.optimized.glb",
"size": 3752136, "size": 3752248,
"checksum": "c452a843c2e4123d4fe0d20da41558f5b5a1906548b3d091dc691ffc42ed19d9" "checksum": "ee81c9e7c94d624977143297ca6c070c40799fdda1c51b92044c70dff53987ea"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "ystra-labyrinth-boss1-visual.glb", "fileName": "ystra-labyrinth-boss1-visual.glb",
"size": 2709352, "size": 2709464,
"sha256": "c8ca72a5fdf487fa71a1aecd1e66ea821183f476540be9a89444b409be957c25", "sha256": "5e35eeccf5e207477bfb3a38dee75d53a5c5467433a90270b79a9ba8619a6260",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "ystra-labyrinth-boss1-visual.optimized.glb", "file": "ystra-labyrinth-boss1-visual.optimized.glb",
"size": 2709352, "size": 2709464,
"checksum": "c8ca72a5fdf487fa71a1aecd1e66ea821183f476540be9a89444b409be957c25" "checksum": "5e35eeccf5e207477bfb3a38dee75d53a5c5467433a90270b79a9ba8619a6260"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "ystra-labyrinth-boss2-visual.glb", "fileName": "ystra-labyrinth-boss2-visual.glb",
"size": 2682688, "size": 2682800,
"sha256": "d52524621220e16d844badc24ee745f4374798031b2b0bc7236decbe3b9588ab", "sha256": "be717e3d6e6301ff60762552283386d73da04240e6a7d1e36933e83e02d0250a",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "ystra-labyrinth-boss2-visual.optimized.glb", "file": "ystra-labyrinth-boss2-visual.optimized.glb",
"size": 2682688, "size": 2682800,
"checksum": "d52524621220e16d844badc24ee745f4374798031b2b0bc7236decbe3b9588ab" "checksum": "be717e3d6e6301ff60762552283386d73da04240e6a7d1e36933e83e02d0250a"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "ystra-labyrinth-boss3-visual.glb", "fileName": "ystra-labyrinth-boss3-visual.glb",
"size": 2682688, "size": 2682800,
"sha256": "d52524621220e16d844badc24ee745f4374798031b2b0bc7236decbe3b9588ab", "sha256": "be717e3d6e6301ff60762552283386d73da04240e6a7d1e36933e83e02d0250a",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "ystra-labyrinth-boss3-visual.optimized.glb", "file": "ystra-labyrinth-boss3-visual.optimized.glb",
"size": 2682688, "size": 2682800,
"checksum": "d52524621220e16d844badc24ee745f4374798031b2b0bc7236decbe3b9588ab" "checksum": "be717e3d6e6301ff60762552283386d73da04240e6a7d1e36933e83e02d0250a"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
@@ -137,8 +137,8 @@
{ {
"role": "visual", "role": "visual",
"fileName": "zurhidon-stronghold-124-visual.glb", "fileName": "zurhidon-stronghold-124-visual.glb",
"size": 17189492, "size": 17189904,
"sha256": "a2d450946ca9687395cf65815b931c7817d9f41041afeb36ba1a351ec24104c4", "sha256": "58be5be365b14e42772a4560c5c1d10fceb8aa8abb2c1771cc21ba2b2e02872a",
"compression": "meshopt+ktx2" "compression": "meshopt+ktx2"
}, },
{ {
@@ -8,8 +8,8 @@
}, },
"optimized": { "optimized": {
"file": "zurhidon-stronghold-124-visual.optimized.glb", "file": "zurhidon-stronghold-124-visual.optimized.glb",
"size": 17189492, "size": 17189904,
"checksum": "a2d450946ca9687395cf65815b931c7817d9f41041afeb36ba1a351ec24104c4" "checksum": "58be5be365b14e42772a4560c5c1d10fceb8aa8abb2c1771cc21ba2b2e02872a"
}, },
"compression": "meshopt+ktx2", "compression": "meshopt+ktx2",
"commands": [ "commands": [
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more