feat: restructure guardian progression system with dynamic element support
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:
@@ -9,14 +9,14 @@ import {
|
||||
getSpireRoomTypeDisplay,
|
||||
SPIRE_CONFIG,
|
||||
} from '../utils/spire-utils';
|
||||
import { isGuardianFloor, getExtendedGuardian, getGuardianForFloor, getGuardianHP, generateGuardianName, generateComboGuardianName, ALL_GUARDIAN_FLOORS } from '../data/guardian-encounters';
|
||||
import { isGuardianFloor, getGuardianForFloor, getGuardianHP, generateGuardianName, getAllGuardianFloors } from '../data/guardian-encounters';
|
||||
|
||||
// ─── Spire Utils ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('getRoomsForFloor', () => {
|
||||
it('should return at least minRoomsPerFloor for non-guardian floors', () => {
|
||||
for (let floor = 1; floor <= 50; floor++) {
|
||||
if (floor % 10 === 0) continue; // Skip guardian floors
|
||||
if (floor % 10 === 0) continue;
|
||||
const rooms = getRoomsForFloor(floor);
|
||||
expect(rooms).toBeGreaterThanOrEqual(SPIRE_CONFIG.minRoomsPerFloor);
|
||||
expect(rooms).toBeLessThanOrEqual(SPIRE_CONFIG.maxRoomsPerFloor + 5);
|
||||
@@ -46,16 +46,17 @@ describe('generateSpireRoomType', () => {
|
||||
it('should return combat for first room on non-guardian floors', () => {
|
||||
for (const floor of [1, 5, 15, 25]) {
|
||||
const roomType = generateSpireRoomType(floor, 0, 10);
|
||||
// First room may be combat, swarm, or speed depending on random
|
||||
expect(['combat', 'swarm', 'speed']).toContain(roomType);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return combat for first room on guardian floors (not last room)', () => {
|
||||
// Floor 50 is a guardian floor, but first room should still be combat
|
||||
const roomType = generateSpireRoomType(50, 0, 10);
|
||||
// First room on guardian floor should not be 'guardian' (last room) and may be combat or swarm depending on random
|
||||
expect(['combat', 'swarm']).toContain(roomType);
|
||||
it('should return valid room type for first room on guardian floors (not last room)', () => {
|
||||
// First room on non-last position should never be 'guardian'
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const roomType = generateSpireRoomType(50, 0, 10);
|
||||
expect(['combat', 'swarm', 'speed']).toContain(roomType);
|
||||
expect(roomType).not.toBe('guardian');
|
||||
}
|
||||
});
|
||||
|
||||
it('should return valid room types', () => {
|
||||
@@ -83,9 +84,7 @@ describe('generateSpireFloorState', () => {
|
||||
});
|
||||
|
||||
it('should generate swarm floor with multiple enemies', () => {
|
||||
// Force swarm by using a non-special room index
|
||||
const state = generateSpireFloorState(20, 1, 10);
|
||||
// Room type depends on random, but enemies should be valid
|
||||
if (state.roomType === 'swarm') {
|
||||
expect(state.enemies.length).toBeGreaterThanOrEqual(3);
|
||||
}
|
||||
@@ -122,7 +121,6 @@ describe('getSpireEnemyBarrier', () => {
|
||||
for (let floor = 15; floor <= 100; floor++) {
|
||||
const barrier = getSpireEnemyBarrier(floor, 'fire');
|
||||
expect(barrier).toBeGreaterThanOrEqual(0);
|
||||
// Use toBeLessThan with a small tolerance for floating point precision
|
||||
expect(barrier).toBeLessThanOrEqual(0.3000000001);
|
||||
}
|
||||
});
|
||||
@@ -182,53 +180,52 @@ describe('isGuardianFloor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExtendedGuardian (procedural combo guardians)', () => {
|
||||
it('should return combo guardians for floors 150+', () => {
|
||||
const g150 = getExtendedGuardian(150);
|
||||
expect(g150).not.toBeNull();
|
||||
expect(g150!.element).toContain('+');
|
||||
});
|
||||
|
||||
it('should return null for floors below 150', () => {
|
||||
expect(getExtendedGuardian(1)).toBeNull();
|
||||
expect(getExtendedGuardian(15)).toBeNull();
|
||||
expect(getExtendedGuardian(95)).toBeNull();
|
||||
expect(getExtendedGuardian(100)).toBeNull();
|
||||
expect(getExtendedGuardian(140)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGuardianForFloor (unified lookup)', () => {
|
||||
it('should return base element guardians for floors 10-70', () => {
|
||||
expect(getGuardianForFloor(10)!.element).toBe('fire');
|
||||
expect(getGuardianForFloor(20)!.element).toBe('water');
|
||||
expect(getGuardianForFloor(30)!.element).toBe('air');
|
||||
expect(getGuardianForFloor(40)!.element).toBe('earth');
|
||||
expect(getGuardianForFloor(50)!.element).toBe('light');
|
||||
expect(getGuardianForFloor(60)!.element).toBe('dark');
|
||||
expect(getGuardianForFloor(70)!.element).toBe('death');
|
||||
expect(getGuardianForFloor(10)!.element).toEqual(['fire']);
|
||||
expect(getGuardianForFloor(20)!.element).toEqual(['water']);
|
||||
expect(getGuardianForFloor(30)!.element).toEqual(['air']);
|
||||
expect(getGuardianForFloor(40)!.element).toEqual(['earth']);
|
||||
expect(getGuardianForFloor(50)!.element).toEqual(['light']);
|
||||
expect(getGuardianForFloor(60)!.element).toEqual(['dark']);
|
||||
expect(getGuardianForFloor(70)!.element).toEqual(['death']);
|
||||
});
|
||||
|
||||
it('should return utility guardian for floor 80', () => {
|
||||
expect(getGuardianForFloor(80)!.element).toBe('transference');
|
||||
expect(getGuardianForFloor(80)!.element).toEqual(['transference']);
|
||||
});
|
||||
|
||||
it('should return compound guardians for floors 90-110', () => {
|
||||
expect(getGuardianForFloor(90)!.element).toBe('metal');
|
||||
expect(getGuardianForFloor(100)!.element).toBe('sand');
|
||||
expect(getGuardianForFloor(110)!.element).toBe('lightning');
|
||||
it('should return composite guardians for floors 90-110', () => {
|
||||
expect(getGuardianForFloor(90)!.element).toEqual(['metal']);
|
||||
expect(getGuardianForFloor(100)!.element).toEqual(['sand']);
|
||||
expect(getGuardianForFloor(110)!.element).toEqual(['lightning']);
|
||||
});
|
||||
|
||||
it('should return exotic guardians for floors 120-140', () => {
|
||||
expect(getGuardianForFloor(120)!.element).toBe('crystal');
|
||||
expect(getGuardianForFloor(130)!.element).toBe('stellar');
|
||||
expect(getGuardianForFloor(140)!.element).toBe('void');
|
||||
it('should return multi-element guardians for tier 3 floors', () => {
|
||||
expect(getGuardianForFloor(130)!.element).toEqual(['metal', 'fire', 'earth']);
|
||||
expect(getGuardianForFloor(140)!.element).toEqual(['sand', 'earth', 'water']);
|
||||
expect(getGuardianForFloor(150)!.element).toEqual(['lightning', 'fire', 'air']);
|
||||
});
|
||||
|
||||
it('should return combo guardians for floors 150+', () => {
|
||||
const g150 = getGuardianForFloor(150);
|
||||
expect(g150).not.toBeNull();
|
||||
expect(g150!.element).toContain('+');
|
||||
it('should return exotic guardians for floors 170-200', () => {
|
||||
expect(getGuardianForFloor(170)!.element).toEqual(['crystal']);
|
||||
expect(getGuardianForFloor(180)!.element).toEqual(['stellar']);
|
||||
expect(getGuardianForFloor(190)!.element).toEqual(['void']);
|
||||
});
|
||||
|
||||
it('should return multi-element exotic convergence for floor 200', () => {
|
||||
const g200 = getGuardianForFloor(200);
|
||||
expect(g200).not.toBeNull();
|
||||
expect(g200!.element.length).toBeGreaterThan(1);
|
||||
expect(g200!.element).toContain('crystal');
|
||||
expect(g200!.element).toContain('stellar');
|
||||
expect(g200!.element).toContain('void');
|
||||
});
|
||||
|
||||
it('should return guardians for procedural floors 210+', () => {
|
||||
const g210 = getGuardianForFloor(210);
|
||||
expect(g210).not.toBeNull();
|
||||
expect(g210!.element.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('should return null for non-guardian floors', () => {
|
||||
@@ -255,36 +252,38 @@ describe('getGuardianHP', () => {
|
||||
describe('generateGuardianName', () => {
|
||||
it('should generate non-empty names', () => {
|
||||
for (const element of ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']) {
|
||||
const name = generateGuardianName(element);
|
||||
const name = generateGuardianName([element]);
|
||||
expect(name).toBeTruthy();
|
||||
expect(name.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should include a title', () => {
|
||||
const name = generateGuardianName('fire');
|
||||
const name = generateGuardianName(['fire']);
|
||||
expect(name).toContain(' the ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateComboGuardianName', () => {
|
||||
it('should combine two element prefixes', () => {
|
||||
const name = generateComboGuardianName(['fire', 'water']);
|
||||
expect(name).toContain(' the ');
|
||||
expect(name.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ALL_GUARDIAN_FLOORS', () => {
|
||||
describe('getAllGuardianFloors', () => {
|
||||
it('should include base guardian floors', () => {
|
||||
expect(ALL_GUARDIAN_FLOORS).toContain(10);
|
||||
expect(ALL_GUARDIAN_FLOORS).toContain(20);
|
||||
expect(ALL_GUARDIAN_FLOORS).toContain(100);
|
||||
const floors = getAllGuardianFloors();
|
||||
expect(floors).toContain(10);
|
||||
expect(floors).toContain(20);
|
||||
expect(floors).toContain(100);
|
||||
});
|
||||
|
||||
it('should be sorted', () => {
|
||||
for (let i = 1; i < ALL_GUARDIAN_FLOORS.length; i++) {
|
||||
expect(ALL_GUARDIAN_FLOORS[i]).toBeGreaterThan(ALL_GUARDIAN_FLOORS[i - 1]);
|
||||
const floors = getAllGuardianFloors();
|
||||
for (let i = 1; i < floors.length; i++) {
|
||||
expect(floors[i]).toBeGreaterThan(floors[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should include procedural floors', () => {
|
||||
const floors = getAllGuardianFloors();
|
||||
expect(floors).toContain(210);
|
||||
expect(floors).toContain(250);
|
||||
expect(floors).toContain(300);
|
||||
expect(floors).toContain(400);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user