Release Healer Man 0.1.8

This commit is contained in:
phenom
2026-08-27 13:29:58 -04:00
parent eca6b5db9b
commit c245a199b6
90 changed files with 8249 additions and 232 deletions
@@ -175,6 +175,10 @@ function interpolate(left, right, alpha) {
return left.map((value, axis) => rounded(value + (right[axis] - value) * alpha));
}
function horizontalDistanceSquared(left, right) {
return (left[0] - right[0]) ** 2 + (left[2] - right[2]) ** 2;
}
function normalizedName(value) {
return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
}
@@ -201,7 +205,7 @@ const ADT_SOURCE_BASES = new Set([
"z-y-negative-x",
]);
const GLOBAL_WMO_SOURCE_BASES = new Set(["draft", "flip-x"]);
const GLOBAL_WMO_ALIGNMENTS = new Set(["player-anchor", "source"]);
const COORDINATE_ALIGNMENTS = new Set(["player-anchor", "source", "source-transform"]);
function sourceBasisFor(recipe, environmentMode) {
if (environmentMode !== "adt-hybrid") {
@@ -222,14 +226,42 @@ function sourceBasisFor(recipe, environmentMode) {
}
function coordinateAlignmentFor(recipe, environmentMode) {
if (environmentMode !== "global-wmo") return "player-anchor";
const alignment = recipe.globalWmoAlignment ?? "player-anchor";
if (!GLOBAL_WMO_ALIGNMENTS.has(alignment)) {
throw new Error(`${recipe.slug}: unsupported global WMO alignment ${alignment}.`);
const legacyGlobalWmoAlignment = environmentMode === "global-wmo"
? recipe.globalWmoAlignment
: undefined;
if (
recipe.coordinateAlignment
&& legacyGlobalWmoAlignment
&& recipe.coordinateAlignment !== legacyGlobalWmoAlignment
) {
throw new Error(`${recipe.slug}: coordinate alignment overrides disagree.`);
}
const alignment = recipe.coordinateAlignment ?? legacyGlobalWmoAlignment ?? "player-anchor";
if (!COORDINATE_ALIGNMENTS.has(alignment)) {
throw new Error(`${recipe.slug}: unsupported coordinate alignment ${alignment}.`);
}
return alignment;
}
function sourceTransformFor(recipe, alignment) {
const transform = recipe.coordinateTransform;
if (alignment !== "source-transform") {
if (transform) {
throw new Error(`${recipe.slug}: coordinateTransform requires source-transform alignment.`);
}
return undefined;
}
if (!transform || !Array.isArray(transform.origin) || transform.origin.length !== 3) {
throw new Error(`${recipe.slug}: source-transform alignment requires a three-value coordinateTransform.origin.`);
}
const origin = transform.origin.map(Number);
const yawDegrees = Number(transform.yawDegrees ?? 0);
if (!origin.every(Number.isFinite) || !Number.isFinite(yawDegrees)) {
throw new Error(`${recipe.slug}: coordinateTransform values must be finite numbers.`);
}
return { origin, yawDegrees };
}
/**
* Runtime drafts retain the original pipeline convention. wow.export ADT
* packages have an additional horizontal basis change baked into their GLBs.
@@ -248,10 +280,39 @@ function sourceToPackageBasis(position, environmentMode, sourceBasis) {
: point;
}
function coordinateMapper(draft, environmentMode, playerAnchor, sourceBasis, alignment) {
function rotateAroundY(position, yawDegrees) {
const radians = yawDegrees * Math.PI / 180;
const cosine = Math.cos(radians);
const sine = Math.sin(radians);
return vector([
position[0] * cosine - position[2] * sine,
position[1],
position[0] * sine + position[2] * cosine,
]);
}
function coordinateMapper(
draft,
environmentMode,
playerAnchor,
sourceBasis,
alignment,
sourceTransform,
) {
if (alignment === "source") {
return (position) => sourceToPackageBasis(position, environmentMode, sourceBasis);
}
if (alignment === "source-transform") {
const basisOrigin = sourceToPackageBasis(
sourceTransform.origin,
environmentMode,
sourceBasis,
);
return (position) => rotateAroundY(
subtract(sourceToPackageBasis(position, environmentMode, sourceBasis), basisOrigin),
sourceTransform.yawDegrees,
);
}
const sourceEntrance = draft?.entrance
?? draft?.spawns?.[0]?.position
?? playerAnchor.position;
@@ -778,6 +839,7 @@ async function compile() {
const environmentMode = inferEnvironmentMode(recipe, assetPackage);
const sourceBasis = sourceBasisFor(recipe, environmentMode);
const coordinateAlignment = coordinateAlignmentFor(recipe, environmentMode);
const sourceTransform = sourceTransformFor(recipe, coordinateAlignment);
const playerAnchor = assetPackage.anchors.find((anchor) => anchor.kind === "player");
const trashAnchor = assetPackage.anchors.find((anchor) => anchor.kind === "trash");
const bossAnchor = assetPackage.anchors.find((anchor) => anchor.kind === "boss");
@@ -788,7 +850,7 @@ async function compile() {
trashAnchor.position[0] - playerAnchor.position[0],
trashAnchor.position[2] - playerAnchor.position[2],
);
const entranceYaw = Number.isFinite(playerAnchor.yaw)
let entranceYaw = Number.isFinite(playerAnchor.yaw)
? playerAnchor.yaw
: inferredEntranceYaw;
@@ -809,6 +871,7 @@ async function compile() {
playerAnchor,
sourceBasis,
coordinateAlignment,
sourceTransform,
);
const entrancePosition = mapPosition(
draft?.entrance
@@ -896,16 +959,49 @@ async function compile() {
const entry = Number(spawn.entityId.replace("entry-", ""));
if (!bossCandidatesByEntry.has(entry)) bossCandidatesByEntry.set(entry, spawn);
}
const mappedNativeRoutePositions = coordinateAlignment !== "player-anchor"
? (draft?.spawns ?? [])
.map((spawn) => mapPosition(spawn.position))
.filter((position) => position.every(Number.isFinite))
.sort((left, right) => (
horizontalDistanceSquared(left, entrancePosition)
- horizontalDistanceSquared(right, entrancePosition)
))
: [];
const entranceDirectionTarget = mappedNativeRoutePositions.find((position) => (
Math.abs(position[1] - entrancePosition[1]) <= 3
&& horizontalDistanceSquared(position, entrancePosition) >= 16
));
if (entranceDirectionTarget) {
entranceYaw = Math.atan2(
entranceDirectionTarget[0] - entrancePosition[0],
entranceDirectionTarget[2] - entrancePosition[2],
);
}
const entranceLevelRoutePositions = mappedNativeRoutePositions.filter(
(position) => Math.abs(position[1] - entrancePosition[1]) <= 2.5,
);
const nativeRoutePositions = (
bossCandidatesByEntry.size === 0
&& entranceLevelRoutePositions.length >= encounters.length
)
? entranceLevelRoutePositions
: mappedNativeRoutePositions;
const usedBossSpawnIds = new Set();
const bossObjectives = encounters.map((encounter, index) => {
const candidate = bossCandidatesByEntry.get(Number(encounter.creatureId));
const alpha = encounters.length <= 1 ? 1 : index / (encounters.length - 1);
const routePosition = interpolate(trashAnchor.position, bossAnchor.position, 0.18 + alpha * 0.82);
const routeAlpha = 0.18 + alpha * 0.82;
const nativeRoutePosition = nativeRoutePositions.length
? nativeRoutePositions[Math.round(routeAlpha * (nativeRoutePositions.length - 1))]
: undefined;
const routePosition = nativeRoutePosition
?? interpolate(trashAnchor.position, bossAnchor.position, routeAlpha);
const position = encounter.eventOnly
? [-151.27, -102.82, 252.26]
: candidate
? mapPosition(candidate.position)
: offsetPosition(routePosition, index % 5, 2.4);
: nativeRoutePosition ?? offsetPosition(routePosition, index % 5, 2.4);
const id = encounter.eventOnly
? "mutanus-spawn"
: candidate?.id ?? `encounter-${encounter.encounterId}`;
@@ -1000,8 +1096,9 @@ async function compile() {
const allPoints = [
entrancePosition,
trashAnchor.position,
bossAnchor.position,
...(coordinateAlignment === "player-anchor" || syntheticTrash
? [trashAnchor.position, bossAnchor.position]
: []),
...staticSpawns.map((spawn) => spawn.position),
...roamingPacks.flatMap((pack) => pack.waypoints),
];
@@ -1032,6 +1129,8 @@ async function compile() {
? ["Trash identities and placements are procedural; boss identities come from the installed Ascension encounter catalog."]
: coordinateAlignment === "source"
? [`Server positions use the packaged ${environmentMode} environment's native world coordinates.`]
: coordinateAlignment === "source-transform"
? [`Server positions use the packaged ${environmentMode} environment's authoritative WDT placement transform.`]
: [`Server positions were aligned to the packaged ${environmentMode} environment at its reviewed player anchor.`]),
];
@@ -1078,6 +1177,7 @@ async function compile() {
serverGameObjects: draft?.gameObjects ?? [],
provenance: {
clientBuild: recipe.clientBuild,
coordinateAlignment,
clientArchiveHashes: assetPackage.source.checksums ?? {},
databaseAdapter: databaseKind,
databaseRevision: draft?.spawns?.length
@@ -1090,6 +1190,8 @@ async function compile() {
? sourceBasis === "z-y-negative-x"
? "three(x,y,z)=(wow.y,wow.z,wow.x)"
: "three(x,y,z)=(-wow.y,wow.z,wow.x)"
: coordinateAlignment === "source-transform"
? `three(x,y,z)=rotate-y(${sourceBasis === "flip-x" ? "(-draft.x,draft.y,draft.z)" : "draft(x,y,z)"}-[${sourceTransform.origin.join(",")}],${sourceTransform.yawDegrees}deg)`
: coordinateAlignment === "source"
? sourceBasis === "flip-x"
? "three(x,y,z)=(-draft.x,draft.y,draft.z)"