Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions api/src/lib/ancientRunicGlassStainedChancelWindow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import crypto from "node:crypto";

/**
* Ancient Runic Glass Stained Chancel Window, Lead Came Assembly & Holy Light Refraction Engine for OpenAO MMORPG.
* Simulates stained window glazier benches and lead came easels (Cedar Window Assembly Bench, Runic Lead Came Easel, Celestial Void Chancel Sanctum),
* stained glass rondels and flashed colored glass panes (Cathedral Blue Rondel, Ruby Red Flashed Glass Pane, Celestial Void Holy Light Plate),
* chancel window and cathedral rose window recipes (Saint Benedict Protective Window, Sunburst Holy Blessing Chancel, Celestial Void Seraphic Rose Window),
* independent holy sanctuary warding ratings (0% to 100%), clamped holy warding and clamped health regen aura scaling,
* upfront glass material deduction on all craft attempts, consistent remainingProvidedGlass return shapes across all paths, cached static catalog maxima, authoritative catalog power ratio, and assembly easel maintenance.
*/

export type WindowAssemblyBenchType = "CEDAR_WINDOW_ASSEMBLY_BENCH" | "RUNIC_LEAD_CAME_EASEL" | "CELESTIAL_VOID_CHANCEL_SANCTUM";
export type RawStainedGlassType = "CATHEDRAL_BLUE_RONDEL" | "RUBY_RED_FLASHED_GLASS_PANE" | "CELESTIAL_VOID_HOLY_LIGHT_PLATE";
export type ChancelWindowRecipeType = "SAINT_BENEDICT_PROTECTIVE_WINDOW" | "SUNBURST_HOLY_BLESSING_CHANCEL" | "CELESTIAL_VOID_SERAPHIC_ROSE_WINDOW";

export interface WindowAssemblyBenchData {
benchType: WindowAssemblyBenchType;
maxDurability: number;
glazieryPower: number;
baseSuccessRatePercent: number; // 0 to 100
sanctityBonusPercent: number;
}

export interface ChancelWindowRecipeData {
recipeType: ChancelWindowRecipeType;
requiredGlassType: RawStainedGlassType;
requiredGlassCount: number;
baseHolyWardingPercent: number;
baseHealthRegenAuraPercent: number;
}

export interface ActiveWindowAssemblyBench {
benchId: string;
glazierPlayerId: string;
benchType: WindowAssemblyBenchType;
currentDurability: number;
maxDurability: number;
glazieryPower: number;
isFunctional: boolean;
}

export interface CraftedChancelWindow {
windowId: string;
recipeType: ChancelWindowRecipeType;
finalHolyWardingPercent: number;
finalHealthRegenAuraPercent: number;
sanctuarySanctityPercent: number; // 0 to 100
consumedGlassCount: number;
consumedGlassType: RawStainedGlassType;
remainingProvidedGlass: RawStainedGlassType[];
craftedEpochMs: number;
}

export const CHANCEL_BENCH_CATALOG: Record<WindowAssemblyBenchType, WindowAssemblyBenchData> = {
CEDAR_WINDOW_ASSEMBLY_BENCH: { benchType: "CEDAR_WINDOW_ASSEMBLY_BENCH", maxDurability: 75, glazieryPower: 25, baseSuccessRatePercent: 85, sanctityBonusPercent: 10 },
RUNIC_LEAD_CAME_EASEL: { benchType: "RUNIC_LEAD_CAME_EASEL", maxDurability: 170, glazieryPower: 65, baseSuccessRatePercent: 92, sanctityBonusPercent: 20 },
CELESTIAL_VOID_CHANCEL_SANCTUM: { benchType: "CELESTIAL_VOID_CHANCEL_SANCTUM", maxDurability: 310, glazieryPower: 120, baseSuccessRatePercent: 99, sanctityBonusPercent: 35 },
};

export const CHANCEL_RECIPE_CATALOG: Record<ChancelWindowRecipeType, ChancelWindowRecipeData> = {
SAINT_BENEDICT_PROTECTIVE_WINDOW: { recipeType: "SAINT_BENEDICT_PROTECTIVE_WINDOW", requiredGlassType: "CATHEDRAL_BLUE_RONDEL", requiredGlassCount: 2, baseHolyWardingPercent: 20, baseHealthRegenAuraPercent: 10 },
SUNBURST_HOLY_BLESSING_CHANCEL: { recipeType: "SUNBURST_HOLY_BLESSING_CHANCEL", requiredGlassType: "RUBY_RED_FLASHED_GLASS_PANE", requiredGlassCount: 2, baseHolyWardingPercent: 45, baseHealthRegenAuraPercent: 25 },
CELESTIAL_VOID_SERAPHIC_ROSE_WINDOW: { recipeType: "CELESTIAL_VOID_SERAPHIC_ROSE_WINDOW", requiredGlassType: "CELESTIAL_VOID_HOLY_LIGHT_PLATE", requiredGlassCount: 2, baseHolyWardingPercent: 85, baseHealthRegenAuraPercent: 60 },
};

export class AncientRunicGlassStainedChancelWindowEngine {
public static readonly DURABILITY_COST_PER_CRAFT = 10;

/**
* Cached static catalog maxima to prevent runtime array reallocation.
*/
public static readonly CATALOG_MAXIMA = {
maxPower: Math.max(...Object.values(CHANCEL_BENCH_CATALOG).map(b => b.glazieryPower), 1),
maxBonus: Math.max(...Object.values(CHANCEL_BENCH_CATALOG).map(b => b.sanctityBonusPercent), 1),
};

/**
* Generates a crypto-secure UUID or 128-bit hex string using node:crypto.
*/
private static generateSecureId(): string {
if (typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
return crypto.randomBytes(16).toString("hex");
}

/**
* Constructs and initializes a stained glass window assembly bench or lead came easel.
*/
public static constructBench(
glazierPlayerId: string,
benchType: WindowAssemblyBenchType
): ActiveWindowAssemblyBench {
const data = CHANCEL_BENCH_CATALOG[benchType];
if (!data) {
throw new Error(`Unsupported window assembly bench type: ${String(benchType)}`);
}

const uuid = this.generateSecureId();

return {
benchId: `bench_${benchType.toLowerCase()}_${uuid}`,
glazierPlayerId,
benchType,
currentDurability: data.maxDurability,
maxDurability: data.maxDurability,
glazieryPower: data.glazieryPower,
isFunctional: true,
};
}

/**
* Assembles and solders stained glass rondels into cathedral chancel windows and rose windows.
* Note: Mutates the passed `bench` in place and returns it as `updatedBench` for caller ergonomics.
*/
public static assembleWindow(
bench: ActiveWindowAssemblyBench,
recipeType: ChancelWindowRecipeType,
providedGlass: RawStainedGlassType[],
craftRoll = Math.random(),
sanctityRoll = Math.random(),
currentEpochMs = Date.now()
): { success: boolean; window?: CraftedChancelWindow; updatedBench?: ActiveWindowAssemblyBench; remainingDurability: number; remainingProvidedGlass: RawStainedGlassType[]; reason?: string } {
const fallbackGlass = Array.isArray(providedGlass) ? [...providedGlass] : [];

if (!bench || !bench.isFunctional || bench.currentDurability < this.DURABILITY_COST_PER_CRAFT) {
return {
success: false,
updatedBench: bench,
remainingDurability: bench?.currentDurability ?? 0,
remainingProvidedGlass: fallbackGlass,
reason: `Window assembly bench is rickety or lacks durability (requires ${this.DURABILITY_COST_PER_CRAFT}).`,
};
}

const benchData = CHANCEL_BENCH_CATALOG[bench.benchType];
if (!benchData) {
return { success: false, updatedBench: bench, remainingDurability: bench.currentDurability, remainingProvidedGlass: fallbackGlass, reason: `Unknown bench model: ${String(bench.benchType)}` };
}

const recipe = CHANCEL_RECIPE_CATALOG[recipeType];
if (!recipe) {
return { success: false, updatedBench: bench, remainingDurability: bench.currentDurability, remainingProvidedGlass: fallbackGlass, reason: `Unknown chancel recipe: ${String(recipeType)}` };
}

if (!Array.isArray(providedGlass)) {
return { success: false, updatedBench: bench, remainingDurability: bench.currentDurability, remainingProvidedGlass: [], reason: "Invalid glass array." };
}

// Count matching glass panes
const matchingCount = providedGlass.filter(g => g === recipe.requiredGlassType).length;
if (matchingCount < recipe.requiredGlassCount) {
return {
success: false,
updatedBench: bench,
remainingDurability: bench.currentDurability,
remainingProvidedGlass: fallbackGlass,
reason: `Insufficient glass pane: requires ${recipe.requiredGlassCount}x ${recipe.requiredGlassType}, provided ${matchingCount}.`,
};
}

// Deduct durability in place
bench.currentDurability -= this.DURABILITY_COST_PER_CRAFT;
if (bench.currentDurability < this.DURABILITY_COST_PER_CRAFT) {
bench.currentDurability = Math.max(0, bench.currentDurability);
bench.isFunctional = false;
}

// Deduct materials upfront on all craft attempts
const remaining = [...providedGlass];
let removed = 0;
for (let i = remaining.length - 1; i >= 0 && removed < recipe.requiredGlassCount; i--) {
if (remaining[i] === recipe.requiredGlassType) {
remaining.splice(i, 1);
removed++;
}
}

const safeRoll = Number.isFinite(craftRoll) ? Math.max(0, Math.min(1, craftRoll)) : Math.random();
const rollPercent = safeRoll * 100;

if (rollPercent > benchData.baseSuccessRatePercent) {
return {
success: false,
updatedBench: bench,
remainingDurability: bench.currentDurability,
remainingProvidedGlass: remaining,
reason: `Lead came buckled: solder seam warped under iron heat, rolled ${rollPercent.toFixed(1)}, needed <= ${benchData.baseSuccessRatePercent}.`,
};
}

// Calculate independent sanctuary sanctity score (0% to 100%) dynamically using cached catalog maxima & authoritative catalog values
const { maxPower, maxBonus } = this.CATALOG_MAXIMA;
const safeSanctityRoll = Number.isFinite(sanctityRoll) ? Math.max(0, Math.min(1, sanctityRoll)) : Math.random();
const powerRatio = Math.min(1.0, benchData.glazieryPower / maxPower);
const bonusPoints = (benchData.sanctityBonusPercent / maxBonus) * 20;
const sanctityScore = Math.max(0, Math.min(100, Math.round(
(safeSanctityRoll * 40) + (powerRatio * 40) + bonusPoints
)));
const qualityMultiplier = 0.8 + ((sanctityScore / 100) * 0.4); // 0.8 to 1.2x

const finalWarding = Math.max(0, Math.min(100, Math.round(recipe.baseHolyWardingPercent * qualityMultiplier)));
const finalRegen = Math.max(0, Math.min(100, Math.round(recipe.baseHealthRegenAuraPercent * qualityMultiplier)));

const uuid = this.generateSecureId();

const chancelWindow: CraftedChancelWindow = {
windowId: `window_${recipeType.toLowerCase()}_${uuid}`,
recipeType,
finalHolyWardingPercent: finalWarding,
finalHealthRegenAuraPercent: finalRegen,
sanctuarySanctityPercent: sanctityScore,
consumedGlassCount: recipe.requiredGlassCount,
consumedGlassType: recipe.requiredGlassType,
remainingProvidedGlass: remaining,
craftedEpochMs: currentEpochMs,
};

return {
success: true,
window: chancelWindow,
updatedBench: bench,
remainingDurability: bench.currentDurability,
remainingProvidedGlass: remaining,
};
}

/**
* Re-leads came tracks and maintains window assembly bench.
*/
public static maintainBench(
bench: ActiveWindowAssemblyBench,
repairAmount = 50
): { success: boolean; newDurability: number; isFunctional: boolean } {
if (!bench) return { success: false, newDurability: 0, isFunctional: false };

const amt = Number.isFinite(repairAmount) ? Math.max(0, repairAmount) : 50;
bench.currentDurability = Math.min(bench.maxDurability, bench.currentDurability + amt);
bench.isFunctional = bench.currentDurability >= this.DURABILITY_COST_PER_CRAFT;

return {
success: true,
newDurability: bench.currentDurability,
isFunctional: bench.isFunctional,
};
}
}
134 changes: 134 additions & 0 deletions api/src/tests/ancientRunicGlassStainedChancelWindow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { describe, it, expect } from "vitest";
import {
AncientRunicGlassStainedChancelWindowEngine,
ActiveWindowAssemblyBench,
} from "../lib/ancientRunicGlassStainedChancelWindow.js";

describe("AncientRunicGlassStainedChancelWindowEngine Window Benches & Rose Windows", () => {
it("assembles Celestial Void Seraphic Rose Window in Chancel Sanctum achieving 100% sanctity and returns spliced plates", () => {
const bench = AncientRunicGlassStainedChancelWindowEngine.constructBench("glazier_01", "CELESTIAL_VOID_CHANCEL_SANCTUM");
expect(bench.benchType).toBe("CELESTIAL_VOID_CHANCEL_SANCTUM");
expect(bench.currentDurability).toBe(310);

const initialPlates = [
"CELESTIAL_VOID_HOLY_LIGHT_PLATE",
"CELESTIAL_VOID_HOLY_LIGHT_PLATE",
"CELESTIAL_VOID_HOLY_LIGHT_PLATE"
] as any[];

const craftRes = AncientRunicGlassStainedChancelWindowEngine.assembleWindow(
bench,
"CELESTIAL_VOID_SERAPHIC_ROSE_WINDOW",
initialPlates,
0.1, // Success roll
1.0, // Sanctity roll 1.0 -> 40 + 40 + 20 = 100%
100000
);

expect(craftRes.success).toBe(true);
expect(craftRes.window?.recipeType).toBe("CELESTIAL_VOID_SERAPHIC_ROSE_WINDOW");
expect(craftRes.window?.sanctuarySanctityPercent).toBe(100);
expect(craftRes.window?.finalHolyWardingPercent).toBe(100); // 85 * 1.20 = 102 -> clamped to 100%
expect(craftRes.window?.finalHealthRegenAuraPercent).toBe(72); // 60 * 1.20 = 72%
expect(craftRes.window?.consumedGlassCount).toBe(2);
expect(craftRes.window?.consumedGlassType).toBe("CELESTIAL_VOID_HOLY_LIGHT_PLATE");
expect(craftRes.window?.remainingProvidedGlass.length).toBe(1);
expect(craftRes.remainingDurability).toBe(300); // 310 - 10
});

it("handles bench becoming non-functional after successful craft when durability falls below threshold", () => {
const bench = AncientRunicGlassStainedChancelWindowEngine.constructBench("glazier_wear", "CEDAR_WINDOW_ASSEMBLY_BENCH");
bench.currentDurability = 15;
expect(bench.isFunctional).toBe(true);

// First craft succeeds: 15 - 10 = 5 (< 10), so isFunctional flips to false
const res1 = AncientRunicGlassStainedChancelWindowEngine.assembleWindow(
bench,
"SAINT_BENEDICT_PROTECTIVE_WINDOW",
["CATHEDRAL_BLUE_RONDEL", "CATHEDRAL_BLUE_RONDEL"],
0.1
);
expect(res1.success).toBe(true);
expect(res1.remainingDurability).toBe(5);
expect(bench.isFunctional).toBe(false);

// Subsequent craft is rejected and returns fallback array
const res2 = AncientRunicGlassStainedChancelWindowEngine.assembleWindow(
bench,
"SAINT_BENEDICT_PROTECTIVE_WINDOW",
["CATHEDRAL_BLUE_RONDEL", "CATHEDRAL_BLUE_RONDEL"]
);
expect(res2.success).toBe(false);
expect(res2.reason).toContain("rickety or lacks durability");
expect(res2.remainingProvidedGlass.length).toBe(2);
});

it("rejects crafting when insufficient glass is provided and returns provided glass", () => {
const bench = AncientRunicGlassStainedChancelWindowEngine.constructBench("glazier_02", "CEDAR_WINDOW_ASSEMBLY_BENCH");

const failRes = AncientRunicGlassStainedChancelWindowEngine.assembleWindow(
bench,
"SUNBURST_HOLY_BLESSING_CHANCEL",
["RUBY_RED_FLASHED_GLASS_PANE"]
);

expect(failRes.success).toBe(false);
expect(failRes.reason).toContain("Insufficient glass pane");
expect(failRes.remainingProvidedGlass.length).toBe(1);
expect(bench.currentDurability).toBe(75);
});

it("handles lead came buckled failure roll consuming durability and glass panes", () => {
const bench = AncientRunicGlassStainedChancelWindowEngine.constructBench("glazier_03", "CEDAR_WINDOW_ASSEMBLY_BENCH"); // 85% success

const fail = AncientRunicGlassStainedChancelWindowEngine.assembleWindow(
bench,
"SAINT_BENEDICT_PROTECTIVE_WINDOW",
["CATHEDRAL_BLUE_RONDEL", "CATHEDRAL_BLUE_RONDEL", "CATHEDRAL_BLUE_RONDEL"],
0.95
);

expect(fail.success).toBe(false);
expect(fail.reason).toContain("buckled");
expect(fail.remainingProvidedGlass?.length).toBe(1); // 3 - 2 = 1 remaining
expect(bench.currentDurability).toBe(65); // 75 - 10
});

it("gates isFunctional in maintainBench based on DURABILITY_COST_PER_CRAFT threshold", () => {
const bench = AncientRunicGlassStainedChancelWindowEngine.constructBench("glazier_04", "CEDAR_WINDOW_ASSEMBLY_BENCH");
bench.currentDurability = 0;
bench.isFunctional = false;

// Maintain 5 (below 10 required) -> isFunctional remains false
const repLow = AncientRunicGlassStainedChancelWindowEngine.maintainBench(bench, 5);
expect(repLow.success).toBe(true);
expect(repLow.newDurability).toBe(5);
expect(repLow.isFunctional).toBe(false);

// Maintain 10 more -> 15 (>= 10) -> isFunctional becomes true
const repHigh = AncientRunicGlassStainedChancelWindowEngine.maintainBench(bench, 10);
expect(repHigh.success).toBe(true);
expect(repHigh.newDurability).toBe(15);
expect(repHigh.isFunctional).toBe(true);
});

it("guards against null inputs and unsupported bench models", () => {
expect(() => AncientRunicGlassStainedChancelWindowEngine.constructBench("g", "PLASTIC_BENCH" as any)).toThrow(
"Unsupported window assembly bench type"
);

const invalidBench: ActiveWindowAssemblyBench = {
benchId: "bad",
glazierPlayerId: "p",
benchType: "BENCH" as any,
currentDurability: 50,
maxDurability: 50,
glazieryPower: 10,
isFunctional: true,
};

expect(AncientRunicGlassStainedChancelWindowEngine.assembleWindow(invalidBench, "SAINT_BENEDICT_PROTECTIVE_WINDOW", ["CATHEDRAL_BLUE_RONDEL", "CATHEDRAL_BLUE_RONDEL"]).success).toBe(false);
expect(AncientRunicGlassStainedChancelWindowEngine.assembleWindow(null as any, "SAINT_BENEDICT_PROTECTIVE_WINDOW", []).success).toBe(false);
expect(AncientRunicGlassStainedChancelWindowEngine.maintainBench(null as any).success).toBe(false);
});
});