From b8e6d651b29e41b50feb98c074489bdfbb8295e2 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Sat, 30 May 2026 10:40:48 +0200 Subject: [PATCH] feat: guardian floors use guardian elements instead of fixed cycle --- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 2 +- .../__tests__/floor-utils.upgraded.test.ts | 93 ++++++++++++------- src/lib/game/stores/index.ts | 1 + src/lib/game/utils/floor-utils.ts | 24 +++++ src/lib/game/utils/index.ts | 2 +- 6 files changed, 88 insertions(+), 36 deletions(-) diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 4558f95..52e6191 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,4 +1,4 @@ # Circular Dependencies -Generated: 2026-05-29T22:16:21.701Z +Generated: 2026-05-29T23:43:49.944Z No circular dependencies found. ✅ diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 585ecee..e108d10 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_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.", "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." }, diff --git a/src/lib/game/__tests__/floor-utils.upgraded.test.ts b/src/lib/game/__tests__/floor-utils.upgraded.test.ts index 336104d..1eb62ff 100644 --- a/src/lib/game/__tests__/floor-utils.upgraded.test.ts +++ b/src/lib/game/__tests__/floor-utils.upgraded.test.ts @@ -4,7 +4,7 @@ // to improve coverage and robustness 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) function guardianHP(floor: number): number { @@ -79,18 +79,30 @@ describe('getFloorMaxHP - Enhanced Edge Cases', () => { // ─── Enhanced getFloorElement Tests ───────────────────────────────────────────── describe('getFloorElement - Enhanced Edge Cases', () => { - it('should cycle correctly through all 7 elements', () => { - const elements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']; - const cycleLength = 7; + it('should cycle correctly through all 7 elements for non-guardian floors', () => { + // Floors 1-9: no guardians, pure cycle based on (floor-1) % 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++) { - expect(getFloorElement(i + 1)).toBe(elements[i]); - } - - // Test the cycle repeats - for (let i = 0; i < cycleLength; i++) { - expect(getFloorElement(i + 1 + cycleLength)).toBe(elements[i]); - } + // Floor 10 is guardian (fire), floors 11-19 continue cycle from their position + expect(getFloorElement(10)).toBe('fire'); // guardian override + expect(getFloorElement(11)).toBe('earth'); // (10 % 7) = 3 + expect(getFloorElement(12)).toBe('light'); // (11 % 7) = 4 + expect(getFloorElement(13)).toBe('dark'); // (12 % 7) = 5 + expect(getFloorElement(14)).toBe('death'); // (13 % 7) = 6 + 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', () => { @@ -113,17 +125,26 @@ describe('getFloorElement - Enhanced Edge Cases', () => { expect(getFloorElement(22)).toBe('fire'); }); - it('should handle very high floor numbers with correct cycle', () => { - // Floor 1000: ((1000-1) % 7 + 7) % 7 = (999 % 7 + 7) % 7 = (5 + 7) % 7 = 5 - // FLOOR_ELEM_CYCLE[5] = 'dark' - expect(getFloorElement(1000)).toBe('dark'); + it('should handle guardian floors with guardian elements', () => { + // Floor 10: Ignis Prime (fire guardian) → returns fire + expect(getFloorElement(10)).toBe('fire'); + // 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 - // FLOOR_ELEM_CYCLE[4] = 'light' + it('should handle very high floor numbers — guardian floors override cycle', () => { + // 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'); - // Floor 1001: ((1001-1) % 7 + 7) % 7 = (1000 % 7 + 7) % 7 = (6 + 7) % 7 = 6 - // FLOOR_ELEM_CYCLE[6] = 'death' + // Floor 1001: NOT a guardian, cycle: ((1001-1) % 7) = 6 → FLOOR_ELEM_CYCLE[6] = 'death' expect(getFloorElement(1001)).toBe('death'); }); @@ -139,22 +160,28 @@ describe('getFloorElement - Enhanced Edge Cases', () => { 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']; - for (let i = 1; i <= 1000; i++) { - const elem = getFloorElement(i); - expect(validElements).toContain(elem); + // Non-guardian floors should still return cycle elements + const nonGuardianFloors = [1,2,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,22,23,24,999,1001]; + for (const f of nonGuardianFloors) { + expect(validElements).toContain(getFloorElement(f)); } }); - it('should maintain consistent cycling for sequential calls', () => { - // Ensure the cycle is consistent across multiple calls - const elements: string[] = []; - for (let i = 1; i <= 21; i++) { - elements.push(getFloorElement(i)); - } - 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']); - expect(elements.slice(14, 21)).toEqual(['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']); + it('should maintain consistent cycling for non-guardian sequential calls', () => { + // Non-guardian floors should still cycle predictably + // Floors 1-9 (no guardian in this range for non-guardian floors 1-9 except 10) + const block1 = [1,2,3,4,5,6,7,8,9].map(f => getFloorElement(f)); + expect(block1).toEqual(['fire','water','air','earth','light','dark','death','fire','water']); + }); + + it('getFloorElements returns array with guardian elements', () => { + // 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']); }); }); diff --git a/src/lib/game/stores/index.ts b/src/lib/game/stores/index.ts index 8726661..95d4262 100755 --- a/src/lib/game/stores/index.ts +++ b/src/lib/game/stores/index.ts @@ -34,6 +34,7 @@ export { fmtDec, getFloorMaxHP, getFloorElement, + getFloorElements, computeMaxMana, computeRegen, computeEffectiveRegen, diff --git a/src/lib/game/utils/floor-utils.ts b/src/lib/game/utils/floor-utils.ts index 75ad219..8638221 100644 --- a/src/lib/game/utils/floor-utils.ts +++ b/src/lib/game/utils/floor-utils.ts @@ -15,9 +15,33 @@ export function getFloorMaxHP(floor: number): number { 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 { + const guardian = getGuardianForFloor(floor); + if (guardian && guardian.element.length > 0) { + return guardian.element[0]; + } const len = FLOOR_ELEM_CYCLE.length; const idx = ((floor - 1) % len + len) % len; 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]]; +} + diff --git a/src/lib/game/utils/index.ts b/src/lib/game/utils/index.ts index af1409d..b8f659a 100644 --- a/src/lib/game/utils/index.ts +++ b/src/lib/game/utils/index.ts @@ -5,7 +5,7 @@ export { fmt, fmtDec, formatSpellCost, getSpellCostColor, formatStudyTime, forma export { createSafeStorage } from './safe-persist'; export { ok, okVoid, fail, failTyped, unwrapOr, isErrorCode, ErrorCode } from './result'; export type { Result, ErrorCodeType } from './result'; -export { getFloorMaxHP, getFloorElement } from './floor-utils'; +export { getFloorMaxHP, getFloorElement, getFloorElements } from './floor-utils'; export { computeMaxMana, computeRegen,