Update Healer Man server

This commit is contained in:
phenom committed 2026-09-20 19:27:37 -04:00
1 parent 97ee0c13f9
commit f864b5aea7
20 files changed
+468 -211

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@ test("ships one lossless object for identical assets and verifies original bytes
const original = Buffer.from("original geometry, textures, and animation\n".repeat(4096));
await writeFile(sourcePath, original);
const common = { sourcePath, bytes: original.length, sha256: createHash("sha256").update(original).digest("hex"), extension: ".glb" };
const files = ["first", "second"].map((id) => ({ ...common, logicalPath: `assets/creatures/${id}/model.glb`, packId: `dungeon:${id}` }));
const files = ["first", "second"].map((id) => ({ ...common, logicalPath: `assets/creatures/${id}/model.glb`, packId: `creatures:${id}` }));
const result = await prepareContentObjects(files, { directory: path.join(directory, "objects"), baseUrl: "/content/bundled" });
assert.equal(result.uniqueObjects, 1);
assert.equal(result.uniqueBytes, original.length);
+49 -19
View File
@@ -78,6 +78,7 @@ function packLabel(packId) {
const [category, id = category] = packId.split(":", 2);
const name = id.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
if (category === "dungeon") return name;
if (category === "creatures") return `${name} Creatures`;
if (packId === "characters:wow335a-v1") return "Extra Wrath Character Models & Skins";
if (packId === "characters:ascension-v1") return "Ascension Character Models & Skins";
if (packId === "characters:runewaker-v1") return "RuneWaker Character Models & Skins";
@@ -107,12 +108,13 @@ export function contentPackForPath(logicalPath) {
return basicWrathHuman ? "core:character-basic" : `characters:${segments[2]}`;
}
if (segments[1] === "creatures" && segments[2]) {
return segments[2] === "shared" ? "shared:creatures" : `dungeon:${segments[2]}`;
return segments[2] === "shared" ? "shared:creatures" : `creatures:${segments[2]}`;
}
if (segments[1] === "game" && segments[2] === "creatures" && segments[3]) {
return segments[3] === "shared" ? "shared:creatures" : `dungeon:${segments[3]}`;
return segments[3] === "shared" ? "shared:creatures" : `creatures:${segments[3]}`;
}
if (segments[1] === "game" && segments[2] === "dungeons" && segments[3]) {
if (segments[4] === "creatures") return `creatures:${segments[3]}`;
return `dungeon:${segments[3]}`;
}
if (segments[1] === "game" && segments[2] === "arenas") return "core:arenas";
@@ -203,44 +205,58 @@ function assetPackId(url) {
/**
* Builds the logical install topology for the 54-dungeon WoW campaign.
* Environment files intentionally remain in their independently downloadable
* Manastorm packs; a dungeon pack owns its creatures and pulls in exactly the
* environment/shared-creature packs referenced by the generated definition.
* Environment and creature files remain independently downloadable. A dungeon
* pack pulls in exactly the environment and creature packs referenced by its
* generated definition, while randomized modes can install creatures alone.
*/
export function campaignDungeonPackDefinitions(definitions) {
const packs = new Map();
for (const definition of definitions) {
const dungeonPackId = `dungeon:${definition.id}`;
if (definition.id === "wailing-caverns") {
const referencedCreaturePacks = new Set([
"creatures:wailing-caverns",
...Object.values(definition.entities ?? {})
.map((entity) => entity.visual?.model?.url)
.filter(Boolean)
.map(assetPackId),
]);
const unexpectedCreaturePacks = [...referencedCreaturePacks]
.filter((id) => id !== "creatures:wailing-caverns" && id !== "shared:creatures");
if (unexpectedCreaturePacks.length) {
throw new Error(`${definition.id} references unexpected creature packs: ${unexpectedCreaturePacks.join(", ")}`);
}
packs.set(dungeonPackId, {
id: dungeonPackId,
label: definition.title ?? packLabel(dungeonPackId),
bundledOnAndroid: true,
dependencies: [],
dependencies: [...referencedCreaturePacks],
});
continue;
}
const environmentUrls = [
...(definition.assets?.visual ?? []).map((asset) => asset.url),
...(definition.assets?.collision ?? []).map((asset) => asset.url),
definition.assets?.navigation?.url,
].filter(Boolean);
const resolvedEnvironmentPacks = environmentUrls.map(assetPackId);
if (!resolvedEnvironmentPacks.length || resolvedEnvironmentPacks.some((id) => !id?.startsWith("manastorm:"))) {
throw new Error(`${definition.id} has an environment asset outside a Manastorm content pack.`);
if (!resolvedEnvironmentPacks.length
|| resolvedEnvironmentPacks.some((id) => !id?.startsWith("manastorm:"))) {
throw new Error(`${definition.id} has an environment asset outside its expected content pack.`);
}
const environmentPackIds = new Set(resolvedEnvironmentPacks);
if (environmentPackIds.size !== 1) {
throw new Error(`${definition.id} must resolve to exactly one Manastorm environment pack.`);
}
const [environmentPackId] = environmentPackIds;
packs.set(environmentPackId, {
id: environmentPackId,
label: packLabel(environmentPackId),
bundledOnAndroid: false,
dependencies: [],
});
if (environmentPackId !== dungeonPackId) {
packs.set(environmentPackId, {
id: environmentPackId,
label: packLabel(environmentPackId),
bundledOnAndroid: false,
dependencies: [],
});
}
const creatureUrls = Object.values(definition.entities ?? {})
.map((entity) => entity.visual?.model?.url)
@@ -249,16 +265,21 @@ export function campaignDungeonPackDefinitions(definitions) {
if (resolvedCreaturePacks.some((id) => !id)) {
throw new Error(`${definition.id} has a creature asset outside a content pack.`);
}
const referencedCreaturePacks = new Set(resolvedCreaturePacks.filter((id) => id !== dungeonPackId));
const unexpectedCreaturePacks = [...referencedCreaturePacks].filter((id) => id !== "shared:creatures");
const referencedCreaturePacks = new Set(resolvedCreaturePacks);
const expectedCreaturePackId = `creatures:${definition.id}`;
const unexpectedCreaturePacks = [...referencedCreaturePacks]
.filter((id) => id !== expectedCreaturePackId && id !== "shared:creatures");
if (unexpectedCreaturePacks.length) {
throw new Error(`${definition.id} references unexpected creature packs: ${unexpectedCreaturePacks.join(", ")}`);
}
packs.set(dungeonPackId, {
id: dungeonPackId,
label: definition.title ?? packLabel(dungeonPackId),
bundledOnAndroid: false,
dependencies: [environmentPackId, ...referencedCreaturePacks],
bundledOnAndroid: definition.id === "wailing-caverns",
dependencies: [
...(environmentPackId === dungeonPackId ? [] : [environmentPackId]),
...referencedCreaturePacks,
],
});
}
return [...packs.values()].sort((left, right) => left.id.localeCompare(right.id));
@@ -350,6 +371,15 @@ export function createContentManifest(files, options = {}) {
});
}
// RuneWaker dungeon definitions are generated TypeScript rather than part of
// the WoW JSON catalog. Their matching creature dependency is recoverable
// from the stable pack namespace without importing runtime code here.
for (const creaturePack of [...packs.values()].filter((pack) => pack.id.startsWith("creatures:"))) {
const dungeonPack = packs.get(`dungeon:${creaturePack.id.slice("creatures:".length)}`);
if (!dungeonPack || dungeonPack.dependencies.includes(creaturePack.id)) continue;
dungeonPack.dependencies = [...dungeonPack.dependencies, creaturePack.id];
}
const sortedPacks = [...packs.values()].sort((left, right) => left.id.localeCompare(right.id));
for (const pack of sortedPacks) {
const objects = new Map(files.filter((file) => file.packId === pack.id)
+38 -7
View File
@@ -26,7 +26,9 @@ test("gives randomized bite-sized its base location dependency closure", () => {
test("assigns stable player-facing packs from logical asset paths", () => {
assert.equal(contentPackForPath("assets/game/dungeons/wailing-caverns/visual.glb"), "dungeon:wailing-caverns");
assert.equal(contentPackForPath("assets/creatures/wailing-caverns/mutanus.glb"), "dungeon:wailing-caverns");
assert.equal(contentPackForPath("assets/creatures/wailing-caverns/mutanus.glb"), "creatures:wailing-caverns");
assert.equal(contentPackForPath("assets/game/creatures/deadmines/mine-rat.glb"), "creatures:deadmines");
assert.equal(contentPackForPath("assets/game/dungeons/deadmines/creatures/mr-smite.glb"), "creatures:deadmines");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/manifest.json"), "core:character-basic");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/human/male/rig.glb"), "core:character-basic");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/textures/character/human/male/humanmaleskin00_00.webp"), "core:character-basic");
@@ -44,6 +46,27 @@ test("assigns stable player-facing packs from logical asset paths", () => {
assert.equal(contentPackForPath("basis/basis_transcoder.wasm"), "core:runtime");
});
test("keeps RuneWaker environments and creatures in independently installable packs", () => {
const files = [
"assets/game/dungeons/forsaken-abbey/visual.glb",
"assets/creatures/forsaken-abbey/ancalon.glb",
].map((logicalPath, index) => ({
logicalPath,
sourcePath: logicalPath,
sha256: String(index + 1).repeat(64),
bytes: 100,
extension: ".glb",
packId: contentPackForPath(logicalPath),
}));
const manifest = createContentManifest(files, { version: "runewaker-creature-split" });
const packs = new Map(manifest.packs.map((pack) => [pack.id, pack]));
assert.deepEqual(packs.get("dungeon:forsaken-abbey")?.dependencies, ["creatures:forsaken-abbey"]);
assert.equal(packs.get("dungeon:forsaken-abbey")?.fileCount, 1);
assert.equal(packs.get("creatures:forsaken-abbey")?.fileCount, 1);
assert.equal(manifest.files["/assets/creatures/forsaken-abbey/ancalon.glb"].packId, "creatures:forsaken-abbey");
});
test("gives every WoW campaign dungeon an exact install dependency closure", async () => {
const campaign = JSON.parse(await readFile(path.join(
PROJECT_ROOT,
@@ -113,6 +136,7 @@ test("gives every WoW campaign dungeon an exact install dependency closure", asy
packId: contentPackForPath(logicalPath),
});
addFile("assets/game/dungeons/wailing-caverns/visual.glb");
for (const creature of wailingCreatureManifest.creatures) addFile(creature.url.slice(1));
for (const dungeon of campaign.definitions) {
for (const asset of [
...dungeon.assets.visual,
@@ -134,20 +158,27 @@ test("gives every WoW campaign dungeon an exact install dependency closure", asy
const pack = packs.get(`dungeon:${dungeon.id}`);
assert.ok(pack, dungeon.id);
if (dungeon.id === "wailing-caverns") {
const creaturePacks = [...new Set([
"creatures:wailing-caverns",
...Object.values(dungeon.entities).flatMap((entity) => (
entity.visual?.model?.url ? [contentPackForPath(entity.visual.model.url.slice(1))] : []
)),
])];
assert.equal(pack.bundledOnAndroid, true);
assert.deepEqual(pack.dependencies, []);
assert.deepEqual(pack.dependencies, creaturePacks);
continue;
}
const environmentPack = contentPackForPath(dungeon.assets.visual[0].url.slice(1));
const needsSharedCreatures = Object.values(dungeon.entities).some((entity) => (
entity.visual?.model?.url?.startsWith("/assets/game/creatures/shared/")
));
const creaturePacks = [...new Set(Object.values(dungeon.entities).flatMap((entity) => (
entity.visual?.model?.url ? [contentPackForPath(entity.visual.model.url.slice(1))] : []
)))];
assert.deepEqual(
pack.dependencies,
[environmentPack, ...(needsSharedCreatures ? ["shared:creatures"] : [])],
[...(environmentPack === pack.id ? [] : [environmentPack]), ...creaturePacks],
dungeon.id,
);
assert.deepEqual(packs.get(environmentPack)?.dependencies, [], dungeon.id);
assert.equal(pack.bundledOnAndroid, dungeon.id === "wailing-caverns");
if (environmentPack !== pack.id) assert.deepEqual(packs.get(environmentPack)?.dependencies, [], dungeon.id);
}
for (const [dungeonId, environmentPack] of [
+3 -2
View File
@@ -46,8 +46,9 @@ export async function verifyReleaseBundle(root) {
fail(`bootstrap packs were ${actualPacks.join(", ")}; expected ${expectedPacks.join(", ")}`);
}
const hiddenPack = actualPacks.find((packId) => (
contentPolicy.hiddenContentPackIds.includes(packId)
|| contentPolicy.hiddenContentPackPrefixes.some((prefix) => packId.startsWith(prefix))
!contentPolicy.bootstrapPackIds.includes(packId)
&& (contentPolicy.hiddenContentPackIds.includes(packId)
|| contentPolicy.hiddenContentPackPrefixes.some((prefix) => packId.startsWith(prefix)))
));
if (hiddenPack) fail(`bootstrap contains hidden pack ${hiddenPack}`);