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

@@ -28,6 +28,7 @@ import { listRunewakerDerivedSupportStylingPolicies } from "./lib/rom-derived-su
import { sourceAnchorSupportGeometry } from "./lib/source-anchor-support.mjs";
import { writeBoundedCollisionGlbs } from "./lib/bounded-collision-glb.mjs";
import { validateNativeAlphaResourceCache } from "./lib/native-alpha-cache-validation.mjs";
import { sourceNavigationAnchors } from "./lib/source-navigation-anchors.mjs";
const argv = process.argv.slice(2);
const fallbackRecipe = path.join(pipelineDirectory, "recipes", "forsaken-abbey.json");
@@ -889,11 +890,15 @@ async function bakeNavigation() {
}));
const navigationFile = path.join(convertedDirectory, slug + "-navigation.glb");
const reportFile = path.join(convertedDirectory, "recast-report.json");
const detourAnchorPositions = recipe.navigation.sourceAnchorComponents
? sourceNavigationAnchors(recipe, (await primaryPlacement()).placement)
: [];
await bakeNavigationFromCollisionAssets({
slug,
collisionAssets,
settings: recipe.navigation,
includeDetourTopology: recipe.navigation.includeDetourTopology === true,
detourAnchorPositions,
output: navigationFile,
reportFile,
source: {
@@ -0,0 +1,55 @@
import { Quaternion, Vector3 } from "three";
export function sourceNavigationAnchors(recipe, placement) {
const policy = recipe.navigation?.sourceAnchorComponents;
if (!policy) return [];
if (policy.kind !== "native-source-anchor-components-v1"
|| recipe.navigation.includeDetourTopology !== true) {
throw new Error("Source anchor components require the supported policy and Detour topology.");
}
const finiteVector = (values, length) => Array.isArray(values)
&& values.length === length && values.every(Number.isFinite);
if (!finiteVector(placement.translation, 3) || !finiteVector(placement.rotation, 4)
|| !finiteVector(placement.scale, 3) || placement.scale.some(value => value === 0)
|| !Number.isFinite(recipe.coordinateSystem.metersPerSourceUnit)
|| recipe.coordinateSystem.metersPerSourceUnit <= 0) {
throw new Error("Source navigation anchors require a finite invertible primary WDB placement.");
}
const transform = sourcePosition => {
if (!finiteVector(sourcePosition, 3)) throw new Error("Source navigation anchor requires finite XYZ coordinates.");
const point = new Vector3(...sourcePosition).sub(new Vector3(...placement.translation))
.applyQuaternion(new Quaternion(...placement.rotation).invert())
.divide(new Vector3(...placement.scale));
const meters = recipe.coordinateSystem.metersPerSourceUnit;
return [point.x * meters, point.y * meters, -point.z * meters];
};
if (!Array.isArray(recipe.source.sourceAnchorSupport?.points)
|| !Number.isFinite(policy.maximumProjectionDistance)
|| policy.maximumProjectionDistance <= 0
|| !Array.isArray(policy.requiredSourcePoints) || !policy.requiredSourcePoints.length) {
throw new Error("Source navigation selection needs native anchor points, a projection limit and required source guards.");
}
const anchors = recipe.source.sourceAnchorSupport.points.map((sourcePosition, index) => ({
id: `native-source-anchor-${index}`, kind: "native-source-anchor",
sourcePosition: [...sourcePosition], position: transform(sourcePosition),
maximumDistance: policy.maximumProjectionDistance,
}));
for (const guard of policy.requiredSourcePoints) {
if (typeof guard.id !== "string" || !guard.id.trim() || !guard.evidence
|| !Number.isFinite(guard.maximumDistance) || guard.maximumDistance <= 0) {
throw new Error("Required source navigation guards need an ID, evidence and a projection limit.");
}
if (guard.kind === "source-entry"
&& JSON.stringify(guard.sourcePosition) !== JSON.stringify(recipe.source.entryCoordinates?.position)) {
throw new Error("The navigation entry guard must match the authoritative source entry.");
}
anchors.push({
...guard, sourcePosition: [...guard.sourcePosition],
position: transform(guard.sourcePosition), required: true,
});
}
if (new Set(anchors.map(anchor => anchor.id)).size !== anchors.length) {
throw new Error("Source navigation anchor IDs must be unique.");
}
return anchors;
}
@@ -0,0 +1,42 @@
import assert from "node:assert/strict";
import test from "node:test";
import { readFile } from "node:fs/promises";
import { sourceNavigationAnchors } from "./source-navigation-anchors.mjs";
test("Raksha anchor policy reproduces source entrance and boss positions with strict guards", async () => {
const recipe = JSON.parse(await readFile(new URL("../recipes/raksha-temple.json", import.meta.url), "utf8"));
const anchors = sourceNavigationAnchors(recipe, {
translation: [1857.20312, 86.8668671, 1274.34656], rotation: [0, 0, 0, 1], scale: [1, 1, 1],
});
const entry = anchors.find(anchor => anchor.id === "lua-entry");
const boss = anchors.find(anchor => anchor.id === "shadoj-source-spawn");
assert.ok(Math.abs(entry.position[1] - 21.01331329) < 1e-8);
assert.ok(Math.abs(entry.position[2] - 11.334656) < 1e-8);
assert.ok(Math.abs(boss.position[0] + 115.04822948) < 1e-8);
assert.equal(entry.maximumDistance, 0.5);
assert.equal(boss.maximumDistance, 0.25);
assert.equal(entry.required, true);
assert.equal(boss.required, true);
const altered = structuredClone(recipe);
altered.source.entryCoordinates.position[1] += 1;
assert.throws(() => sourceNavigationAnchors(altered, {
translation: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1],
}), /must match the authoritative source entry/);
});
test("source anchor conversion includes inverse parent rotation, scale and handedness", () => {
const recipe = {
coordinateSystem: { metersPerSourceUnit: 0.1 },
source: { sourceAnchorSupport: { points: [[14, 8, 18]] } },
navigation: { includeDetourTopology: true, sourceAnchorComponents: {
kind: "native-source-anchor-components-v1", maximumProjectionDistance: 4,
requiredSourcePoints: [{ id: "guard", sourcePosition: [14, 8, 18], maximumDistance: 0.5, evidence: "fixture" }],
} },
};
const anchors = sourceNavigationAnchors(recipe, {
translation: [10, 2, 20], rotation: [0, Math.SQRT1_2, 0, Math.SQRT1_2], scale: [2, 3, 4],
});
for (const [axis, expected] of [0.1, 0.2, -0.1].entries()) {
assert.ok(Math.abs(anchors[0].position[axis] - expected) < 1e-8);
}
});
@@ -689,6 +689,27 @@
"agentRadius": 0.36,
"agentMaxClimb": 0.5,
"agentMaxSlope": 50,
"includeDetourTopology": true,
"sourceAnchorComponents": {
"kind": "native-source-anchor-components-v1",
"maximumProjectionDistance": 4,
"requiredSourcePoints": [
{
"id": "lua-entry",
"kind": "source-entry",
"sourcePosition": [1857, 297, 1161],
"maximumDistance": 0.5,
"evidence": "Resource/luascript/03246.lua:MS_TEST_DUNGEONS"
},
{
"id": "shadoj-source-spawn",
"kind": "native-boss",
"sourcePosition": [706.7208251953125, 186.57839965820312, -1572.79296875],
"maximumDistance": 0.25,
"evidence": "ROM_Global.dbo.NPCData source spawn 41884920; raksha-temple-zone-130 snapshot"
}
]
},
"tileSize": 64,
"retainTileIntermediates": false
},