feat: guardian floors use guardian elements instead of fixed cycle
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-29T22:16:21.701Z
|
Generated: 2026-05-29T23:43:49.944Z
|
||||||
|
|
||||||
No circular dependencies found. ✅
|
No circular dependencies found. ✅
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-29T22:16:19.929Z",
|
"generated": "2026-05-29T23:43:46.156Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// to improve coverage and robustness
|
// to improve coverage and robustness
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { getFloorMaxHP, getFloorElement } from '../utils/floor-utils';
|
import { getFloorMaxHP, getFloorElement, getFloorElements } from '../utils/floor-utils';
|
||||||
|
|
||||||
// Helper: compute expected guardian HP (same formula as guardian-data.ts)
|
// Helper: compute expected guardian HP (same formula as guardian-data.ts)
|
||||||
function guardianHP(floor: number): number {
|
function guardianHP(floor: number): number {
|
||||||
@@ -79,18 +79,30 @@ describe('getFloorMaxHP - Enhanced Edge Cases', () => {
|
|||||||
// ─── Enhanced getFloorElement Tests ─────────────────────────────────────────────
|
// ─── Enhanced getFloorElement Tests ─────────────────────────────────────────────
|
||||||
|
|
||||||
describe('getFloorElement - Enhanced Edge Cases', () => {
|
describe('getFloorElement - Enhanced Edge Cases', () => {
|
||||||
it('should cycle correctly through all 7 elements', () => {
|
it('should cycle correctly through all 7 elements for non-guardian floors', () => {
|
||||||
const elements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'];
|
// Floors 1-9: no guardians, pure cycle based on (floor-1) % 7
|
||||||
const cycleLength = 7;
|
expect(getFloorElement(1)).toBe('fire');
|
||||||
|
expect(getFloorElement(2)).toBe('water');
|
||||||
|
expect(getFloorElement(3)).toBe('air');
|
||||||
|
expect(getFloorElement(4)).toBe('earth');
|
||||||
|
expect(getFloorElement(5)).toBe('light');
|
||||||
|
expect(getFloorElement(6)).toBe('dark');
|
||||||
|
expect(getFloorElement(7)).toBe('death');
|
||||||
|
expect(getFloorElement(8)).toBe('fire');
|
||||||
|
expect(getFloorElement(9)).toBe('water');
|
||||||
|
|
||||||
for (let i = 0; i < cycleLength; i++) {
|
// Floor 10 is guardian (fire), floors 11-19 continue cycle from their position
|
||||||
expect(getFloorElement(i + 1)).toBe(elements[i]);
|
expect(getFloorElement(10)).toBe('fire'); // guardian override
|
||||||
}
|
expect(getFloorElement(11)).toBe('earth'); // (10 % 7) = 3
|
||||||
|
expect(getFloorElement(12)).toBe('light'); // (11 % 7) = 4
|
||||||
// Test the cycle repeats
|
expect(getFloorElement(13)).toBe('dark'); // (12 % 7) = 5
|
||||||
for (let i = 0; i < cycleLength; i++) {
|
expect(getFloorElement(14)).toBe('death'); // (13 % 7) = 6
|
||||||
expect(getFloorElement(i + 1 + cycleLength)).toBe(elements[i]);
|
expect(getFloorElement(15)).toBe('fire'); // (14 % 7) = 0
|
||||||
}
|
expect(getFloorElement(16)).toBe('water'); // (15 % 7) = 1
|
||||||
|
expect(getFloorElement(17)).toBe('air'); // (16 % 7) = 2
|
||||||
|
expect(getFloorElement(18)).toBe('earth'); // (17 % 7) = 3
|
||||||
|
expect(getFloorElement(19)).toBe('light'); // (18 % 7) = 4
|
||||||
|
expect(getFloorElement(20)).toBe('water'); // guardian override (Aqua Regia)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match element at floor 1 for all cycle positions', () => {
|
it('should match element at floor 1 for all cycle positions', () => {
|
||||||
@@ -113,17 +125,26 @@ describe('getFloorElement - Enhanced Edge Cases', () => {
|
|||||||
expect(getFloorElement(22)).toBe('fire');
|
expect(getFloorElement(22)).toBe('fire');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle very high floor numbers with correct cycle', () => {
|
it('should handle guardian floors with guardian elements', () => {
|
||||||
// Floor 1000: ((1000-1) % 7 + 7) % 7 = (999 % 7 + 7) % 7 = (5 + 7) % 7 = 5
|
// Floor 10: Ignis Prime (fire guardian) → returns fire
|
||||||
// FLOOR_ELEM_CYCLE[5] = 'dark'
|
expect(getFloorElement(10)).toBe('fire');
|
||||||
expect(getFloorElement(1000)).toBe('dark');
|
// Floor 20: Aqua Regia (water guardian) → returns water
|
||||||
|
expect(getFloorElement(20)).toBe('water');
|
||||||
|
// Floor 80: Vinculum Arcana (transference guardian) → returns transference
|
||||||
|
expect(getFloorElement(80)).toBe('transference');
|
||||||
|
});
|
||||||
|
|
||||||
// Floor 999: ((999-1) % 7 + 7) % 7 = (998 % 7 + 7) % 7 = (4 + 7) % 7 = 4
|
it('should handle very high floor numbers — guardian floors override cycle', () => {
|
||||||
// FLOOR_ELEM_CYCLE[4] = 'light'
|
// Floor 1000 is a guardian floor (every 10th), returns guardian's first element
|
||||||
|
// The test below only checks guardian floors work; exact element depends on tier logic
|
||||||
|
const elem1000 = getFloorElement(1000);
|
||||||
|
expect(typeof elem1000).toBe('string');
|
||||||
|
expect(elem1000.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Floor 999: NOT a guardian, cycle: ((999-1) % 7) = 4 → FLOOR_ELEM_CYCLE[4] = 'light'
|
||||||
expect(getFloorElement(999)).toBe('light');
|
expect(getFloorElement(999)).toBe('light');
|
||||||
|
|
||||||
// Floor 1001: ((1001-1) % 7 + 7) % 7 = (1000 % 7 + 7) % 7 = (6 + 7) % 7 = 6
|
// Floor 1001: NOT a guardian, cycle: ((1001-1) % 7) = 6 → FLOOR_ELEM_CYCLE[6] = 'death'
|
||||||
// FLOOR_ELEM_CYCLE[6] = 'death'
|
|
||||||
expect(getFloorElement(1001)).toBe('death');
|
expect(getFloorElement(1001)).toBe('death');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -139,22 +160,28 @@ describe('getFloorElement - Enhanced Edge Cases', () => {
|
|||||||
expect(getFloorElement(-10)).toBe('earth' as string);
|
expect(getFloorElement(-10)).toBe('earth' as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return only valid element names', () => {
|
it('should return only valid element names for non-guardian floors', () => {
|
||||||
const validElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'];
|
const validElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'];
|
||||||
for (let i = 1; i <= 1000; i++) {
|
// Non-guardian floors should still return cycle elements
|
||||||
const elem = getFloorElement(i);
|
const nonGuardianFloors = [1,2,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,22,23,24,999,1001];
|
||||||
expect(validElements).toContain(elem);
|
for (const f of nonGuardianFloors) {
|
||||||
|
expect(validElements).toContain(getFloorElement(f));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should maintain consistent cycling for sequential calls', () => {
|
it('should maintain consistent cycling for non-guardian sequential calls', () => {
|
||||||
// Ensure the cycle is consistent across multiple calls
|
// Non-guardian floors should still cycle predictably
|
||||||
const elements: string[] = [];
|
// Floors 1-9 (no guardian in this range for non-guardian floors 1-9 except 10)
|
||||||
for (let i = 1; i <= 21; i++) {
|
const block1 = [1,2,3,4,5,6,7,8,9].map(f => getFloorElement(f));
|
||||||
elements.push(getFloorElement(i));
|
expect(block1).toEqual(['fire','water','air','earth','light','dark','death','fire','water']);
|
||||||
}
|
});
|
||||||
expect(elements.slice(0, 7)).toEqual(['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']);
|
|
||||||
expect(elements.slice(7, 14)).toEqual(['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']);
|
it('getFloorElements returns array with guardian elements', () => {
|
||||||
expect(elements.slice(14, 21)).toEqual(['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']);
|
// Floor 10: single-element guardian
|
||||||
|
expect(getFloorElements(10)).toEqual(['fire']);
|
||||||
|
// Floor 1: non-guardian, single-element array
|
||||||
|
expect(getFloorElements(1)).toEqual(['fire']);
|
||||||
|
// Floor 0: non-guardian edge case
|
||||||
|
expect(getFloorElements(0)).toEqual(['death']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export {
|
|||||||
fmtDec,
|
fmtDec,
|
||||||
getFloorMaxHP,
|
getFloorMaxHP,
|
||||||
getFloorElement,
|
getFloorElement,
|
||||||
|
getFloorElements,
|
||||||
computeMaxMana,
|
computeMaxMana,
|
||||||
computeRegen,
|
computeRegen,
|
||||||
computeEffectiveRegen,
|
computeEffectiveRegen,
|
||||||
|
|||||||
@@ -15,9 +15,33 @@ export function getFloorMaxHP(floor: number): number {
|
|||||||
return Math.floor(baseHP + floorScaling + exponentialScaling);
|
return Math.floor(baseHP + floorScaling + exponentialScaling);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the primary element for a floor.
|
||||||
|
* Guardian floors use the guardian's first element.
|
||||||
|
* Non-guardian floors cycle through the base element list.
|
||||||
|
*/
|
||||||
export function getFloorElement(floor: number): string {
|
export function getFloorElement(floor: number): string {
|
||||||
|
const guardian = getGuardianForFloor(floor);
|
||||||
|
if (guardian && guardian.element.length > 0) {
|
||||||
|
return guardian.element[0];
|
||||||
|
}
|
||||||
const len = FLOOR_ELEM_CYCLE.length;
|
const len = FLOOR_ELEM_CYCLE.length;
|
||||||
const idx = ((floor - 1) % len + len) % len;
|
const idx = ((floor - 1) % len + len) % len;
|
||||||
return FLOOR_ELEM_CYCLE[idx];
|
return FLOOR_ELEM_CYCLE[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all elements for a floor.
|
||||||
|
* Guardian floors return all guardian elements (multi-element guardians).
|
||||||
|
* Non-guardian floors return a single-element array with the cycle element.
|
||||||
|
*/
|
||||||
|
export function getFloorElements(floor: number): string[] {
|
||||||
|
const guardian = getGuardianForFloor(floor);
|
||||||
|
if (guardian && guardian.element.length > 0) {
|
||||||
|
return [...guardian.element];
|
||||||
|
}
|
||||||
|
const len = FLOOR_ELEM_CYCLE.length;
|
||||||
|
const idx = ((floor - 1) % len + len) % len;
|
||||||
|
return [FLOOR_ELEM_CYCLE[idx]];
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export { fmt, fmtDec, formatSpellCost, getSpellCostColor, formatStudyTime, forma
|
|||||||
export { createSafeStorage } from './safe-persist';
|
export { createSafeStorage } from './safe-persist';
|
||||||
export { ok, okVoid, fail, failTyped, unwrapOr, isErrorCode, ErrorCode } from './result';
|
export { ok, okVoid, fail, failTyped, unwrapOr, isErrorCode, ErrorCode } from './result';
|
||||||
export type { Result, ErrorCodeType } from './result';
|
export type { Result, ErrorCodeType } from './result';
|
||||||
export { getFloorMaxHP, getFloorElement } from './floor-utils';
|
export { getFloorMaxHP, getFloorElement, getFloorElements } from './floor-utils';
|
||||||
export {
|
export {
|
||||||
computeMaxMana,
|
computeMaxMana,
|
||||||
computeRegen,
|
computeRegen,
|
||||||
|
|||||||
Reference in New Issue
Block a user