From 8b9c95b24f9a8ccbcaf4bed2d0ffe3646d5041d9 Mon Sep 17 00:00:00 2001 From: phenom Date: Sun, 20 Sep 2026 14:13:38 -0400 Subject: [PATCH] Update Healer Man server --- src/game/biteSizedStockades.test.ts | 5 ++-- src/game/dungeons/biteSizedStockades.ts | 10 ++++--- src/game/stockadesNavigation.test.ts | 40 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/game/biteSizedStockades.test.ts b/src/game/biteSizedStockades.test.ts index da336964..2b36c8a0 100644 --- a/src/game/biteSizedStockades.test.ts +++ b/src/game/biteSizedStockades.test.ts @@ -34,8 +34,9 @@ describe("bite-sized Stormwind Stockades", () => { expect(biteSized.objectiveOrder).toEqual(biteSized.bosses); expect(biteSized.staticSpawns.filter((spawn) => spawn.id === bossId)).toHaveLength(1); expect(biteSized.staticSpawns.every((spawn) => Math.abs(spawn.position[2]) <= 15)).toBe(true); - expect(biteSized.navigationLinks.map((link) => link.id)).toEqual(["entrance-to-central-stairs"]); - expect(biteSized.traversalRamps?.map((ramp) => ramp.id)).toEqual(["entrance-stair-ramp"]); + expect(biteSized.navigationLinks).toEqual(source.navigationLinks); + expect(biteSized.traversalRamps).toEqual(source.traversalRamps); + expect(biteSized.roamingPacks).toEqual([]); }); it("seals both side wings with visible movement and sight blockers", () => { diff --git a/src/game/dungeons/biteSizedStockades.ts b/src/game/dungeons/biteSizedStockades.ts index c52944e8..30c112f1 100644 --- a/src/game/dungeons/biteSizedStockades.ts +++ b/src/game/dungeons/biteSizedStockades.ts @@ -133,8 +133,10 @@ export function createBiteSizedStockadesDefinition( roamingPacks: [], bosses: [boss], objectiveOrder: [boss], - navigationLinks: source.navigationLinks.filter((link) => link.id === "entrance-to-central-stairs"), - traversalRamps: source.traversalRamps?.filter((ramp) => ramp.id === "entrance-stair-ramp"), + // Keep the complete reviewed Stockades navigation graph connected. The + // physical branch walls, not navmesh pruning, close the unused wings. + navigationLinks: source.navigationLinks, + traversalRamps: source.traversalRamps, encounters: [{ id: `${BITE_SIZED_STOCKADES_DUNGEON_ID}:boss-progression`, dungeonId: BITE_SIZED_STOCKADES_DUNGEON_ID, @@ -147,8 +149,8 @@ export function createBiteSizedStockadesDefinition( files: ["src/game/dungeons/biteSizedStockades.ts"], reviewedBehavior: "A single seeded Stockades boss completes the bite-sized run in the first boss room.", adaptations: [ - "Each seeded run retains the selected source boss objective ID so saves, loot, guides, and leaderboards report the correct variant.", - "The two side-wing stairs are permanently sealed and omitted from party navigation.", + "Each seeded run retains the selected source boss objective ID so saves, loot, guides, and leaderboards report the correct variant.", + "The side-wing stairs retain their navigation bridges so the central route stays on the retained navmesh component; physical walls seal both unused wings.", ], remainingGaps: [], }, diff --git a/src/game/stockadesNavigation.test.ts b/src/game/stockadesNavigation.test.ts index 1a4e9c84..1905e74d 100644 --- a/src/game/stockadesNavigation.test.ts +++ b/src/game/stockadesNavigation.test.ts @@ -29,7 +29,9 @@ const STOCKADES_NAVIGATION = "public/assets/game/manastorm/34-stormwindjail/navi const TANK_RANGE = Object.freeze({ minimum: 0, preferred: 2.9, maximum: 3.15 }); const stockades = requireDungeonDefinition("stormwind-stockade"); +const biteSizedStockades = requireDungeonDefinition("bite-sized-stormwind-stockade"); let graph: PartyNavigationGraph; +let biteSizedGraph: PartyNavigationGraph; let sourceCollision: Group; beforeAll(async () => { @@ -64,6 +66,10 @@ beforeAll(async () => { graph = preparePartyNavigationGraph( "/test/stockades-navigation.glb", scene, stockades.navigationLinks, stockades.traversalRamps, ); + biteSizedGraph = preparePartyNavigationGraph( + "/test/bite-sized-stockades-navigation.glb", scene, + biteSizedStockades.navigationLinks, biteSizedStockades.traversalRamps, + ); geometry.dispose(); const collisionDocument = await new NodeIO() @@ -256,6 +262,40 @@ describe("Stormwind Stockade party navigation", () => { expect(graph.triangleNodes?.length).toBe(graph.triangles?.length); }); + it("keeps the bite-sized entrance and boss room on the retained navigation surface", () => { + const world = collisionWorld(); + try { + for (const wall of biteSizedStockades.serverGameObjects.filter((object) => object.id.startsWith("bite-sized-"))) { + world.createCollider(RAPIER.ColliderDesc.cuboid(...wall.halfExtents) + .setTranslation(...wall.position) + .setCollisionGroups(DUNGEON_PHYSICAL_COLLISION_GROUPS)); + } + world.step(); + const entrance = findNearestPartyNavigationSurface( + biteSizedGraph, + biteSizedStockades.entrance.footPosition, + 6, + ); + const boss = findNearestPartyNavigationSurface( + biteSizedGraph, + biteSizedStockades.bosses[0].position, + 15, + ); + expect(entrance).not.toBeNull(); + expect(boss).not.toBeNull(); + expect(findPartyNavigationPath( + biteSizedGraph, + entrance!.position, + boss!.position, + { + maxStartSnapDistance: 0.1, + maxEndSnapDistance: 0.1, + routeSegmentAllowed: createPartyRouteSegmentValidator(RAPIER, world), + }, + )).not.toBeNull(); + } finally { world.free(); } + }); + it("projects every entrance formation slot onto the retained walkable surface", () => { const slots = derivePartySpawnSlots({ footPosition: stockades.entrance.footPosition,