docs: add comprehensive game briefing document and fix deprecated tests
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 25s

- Created GAME_BRIEFING.md with full documentation of all game systems
- Fixed getFloorElement to use cycle length instead of hardcoded 8
- Fixed deprecated tests referencing removed elements (life, blood, wood)
- Fixed deprecated tests referencing removed skills (deepReservoir, etc.)
- Fixed guardian tests to not expect floor 70
- Fixed computeRegen tests to account for attunement regen correctly
- All 512 tests now pass

The game briefing document includes:
- Core game loop and progression
- Mana system with all 14 mana types
- Time and incursion mechanics
- Spire and floor system with room types
- Combat system with elemental effectiveness
- Guardian and pact system
- Attunement system (Enchanter, Invoker, Fabricator)
- Skill evolution with 5 tiers and milestone upgrades
- Equipment and enchantment system
- Golemancy system
- Prestige/loop mechanics
- Complete formulas and system interactions
This commit is contained in:
Z User
2026-04-03 22:01:46 +00:00
parent c5dbd0606a
commit ae780e1bbc
8 changed files with 1137 additions and 1238 deletions

View File

@@ -221,7 +221,8 @@ describe('computeRegen', () => {
};
const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 };
const result = computeRegen(state, effects);
expect(result).toBe(2); // Base regen
// Base regen is 2 (this test provides effects, so no attunement bonus)
expect(result).toBe(2);
});
});

View File

@@ -78,7 +78,7 @@ export function getFloorMaxHP(floor: number): number {
}
export function getFloorElement(floor: number): string {
return FLOOR_ELEM_CYCLE[(floor - 1) % 8];
return FLOOR_ELEM_CYCLE[(floor - 1) % FLOOR_ELEM_CYCLE.length];
}
// ─── Equipment Spell Helper ─────────────────────────────────────────────────────

File diff suppressed because it is too large Load Diff

View File

@@ -261,17 +261,17 @@ describe('SkillStore', () => {
it('should not start studying without prerequisites', () => {
useManaStore.getState().addRawMana(990, 1000);
// deepReservoir requires manaWell 5
const result = useSkillStore.getState().startStudyingSkill('deepReservoir', 1000);
// manaOverflow requires manaWell 3
const result = useSkillStore.getState().startStudyingSkill('manaOverflow', 1000);
expect(result.started).toBe(false);
});
it('should start studying with prerequisites met', () => {
useManaStore.getState().addRawMana(990, 1000);
useSkillStore.getState().setSkillLevel('manaWell', 5);
useSkillStore.getState().setSkillLevel('manaWell', 3);
const result = useSkillStore.getState().startStudyingSkill('deepReservoir', 1000);
const result = useSkillStore.getState().startStudyingSkill('manaOverflow', 1000);
expect(result.started).toBe(true);
});

View File

@@ -61,17 +61,17 @@ describe('SkillStore', () => {
it('should not start studying without prerequisites', () => {
const skillStore = useSkillStore.getState();
// deepReservoir requires manaWell level 5
const result = skillStore.startStudyingSkill('deepReservoir', 1000);
// manaOverflow requires manaWell level 3
const result = skillStore.startStudyingSkill('manaOverflow', 1000);
expect(result.started).toBe(false);
});
it('should start studying with prerequisites met', () => {
useSkillStore.setState({ skills: { manaWell: 5 } });
useSkillStore.setState({ skills: { manaWell: 3 } });
const skillStore = useSkillStore.getState();
const result = skillStore.startStudyingSkill('deepReservoir', 1000);
const result = skillStore.startStudyingSkill('manaOverflow', 1000);
expect(result.started).toBe(true);
});
@@ -271,36 +271,36 @@ describe('ManaStore', () => {
describe('craftComposite', () => {
it('should craft composite element with correct ingredients', () => {
// Set up ingredients for blood (life + water)
// Set up ingredients for metal (fire + earth)
useManaStore.setState({
elements: {
...useManaStore.getState().elements,
life: { current: 5, max: 10, unlocked: true },
water: { current: 5, max: 10, unlocked: true },
fire: { current: 5, max: 10, unlocked: true },
earth: { current: 5, max: 10, unlocked: true },
}
});
const result = useManaStore.getState().craftComposite('blood', ['life', 'water']);
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
expect(result).toBe(true);
const state = useManaStore.getState();
expect(state.elements.life.current).toBe(4);
expect(state.elements.water.current).toBe(4);
expect(state.elements.blood.current).toBe(1);
expect(state.elements.blood.unlocked).toBe(true);
expect(state.elements.fire.current).toBe(4);
expect(state.elements.earth.current).toBe(4);
expect(state.elements.metal.current).toBe(1);
expect(state.elements.metal.unlocked).toBe(true);
});
it('should not craft without ingredients', () => {
useManaStore.setState({
elements: {
...useManaStore.getState().elements,
life: { current: 0, max: 10, unlocked: true },
water: { current: 0, max: 10, unlocked: true },
fire: { current: 0, max: 10, unlocked: true },
earth: { current: 0, max: 10, unlocked: true },
}
});
const result = useManaStore.getState().craftComposite('blood', ['life', 'water']);
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
expect(result).toBe(false);
});

View File

@@ -2,6 +2,7 @@
* Comprehensive Store Tests
*
* Tests the split store architecture to ensure all stores work correctly together.
* Updated for the new skill system with tiers and upgrade trees.
*/
import { describe, it, expect, beforeEach } from 'bun:test';
@@ -89,8 +90,20 @@ function createMockState(overrides: Partial<GameState> = {}): GameState {
autoSchedule: false,
studyQueue: [],
craftQueue: [],
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
invoker: { id: 'invoker', active: false, level: 1, experience: 0 },
fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 },
},
golemancy: {
enabledGolems: [],
summonedGolems: [],
lastSummonFloor: 0,
},
equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null },
equipmentInstances: {},
...overrides,
};
} as GameState;
}
// ─── Utility Function Tests ─────────────────────────────────────────────────
@@ -140,43 +153,42 @@ describe('Mana Calculations', () => {
expect(computeMaxMana(state)).toBe(100 + 5 * 100);
});
it('should add mana from deepReservoir skill', () => {
const state = createMockState({ skills: { deepReservoir: 3 } });
expect(computeMaxMana(state)).toBe(100 + 3 * 500);
});
it('should add mana from prestige upgrades', () => {
const state = createMockState({ prestigeUpgrades: { manaWell: 3 } });
expect(computeMaxMana(state)).toBe(100 + 3 * 500);
});
it('should stack all mana bonuses', () => {
it('should stack manaWell skill and prestige', () => {
const state = createMockState({
skills: { manaWell: 5, deepReservoir: 2 },
skills: { manaWell: 5 },
prestigeUpgrades: { manaWell: 2 },
});
expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500 + 2 * 500);
expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500);
});
});
describe('computeRegen', () => {
it('should return base regen with no upgrades', () => {
// Base regen is 2 (attunement regen is added separately in the store)
const state = createMockState();
expect(computeRegen(state)).toBe(2);
});
it('should add regen from manaFlow skill', () => {
// Base 2 + manaFlow 5
const state = createMockState({ skills: { manaFlow: 5 } });
expect(computeRegen(state)).toBe(2 + 5 * 1);
});
it('should add regen from manaSpring skill', () => {
// Base 2 + manaSpring 2
const state = createMockState({ skills: { manaSpring: 1 } });
expect(computeRegen(state)).toBe(2 + 2);
});
it('should multiply by temporal echo prestige', () => {
const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } });
// Base 2 * 1.2 = 2.4
expect(computeRegen(state)).toBe(2 * 1.2);
});
});
@@ -231,19 +243,6 @@ describe('Combat Calculations', () => {
expect(dmg).toBeGreaterThanOrEqual(5); // Base damage (can be higher with crit)
});
it('should add damage from combatTrain skill', () => {
const state = createMockState({ skills: { combatTrain: 5 } });
const dmg = calcDamage(state, 'manaBolt');
expect(dmg).toBeGreaterThanOrEqual(5 + 5 * 5); // 5 base + 25 from skill
});
it('should multiply by arcaneFury skill', () => {
const state = createMockState({ skills: { arcaneFury: 3 } });
const dmg = calcDamage(state, 'manaBolt');
// 5 * 1.3 = 6.5 minimum (without crit)
expect(dmg).toBeGreaterThanOrEqual(5 * 1.3 * 0.8);
});
it('should have elemental bonuses', () => {
const state = createMockState({
spells: {
@@ -280,15 +279,22 @@ describe('Combat Calculations', () => {
describe('getFloorElement', () => {
it('should cycle through elements in order', () => {
// FLOOR_ELEM_CYCLE has 7 elements: fire, water, air, earth, light, dark, death
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');
});
it('should wrap around after 8 floors', () => {
expect(getFloorElement(9)).toBe('fire');
expect(getFloorElement(10)).toBe('water');
it('should wrap around after 7 floors', () => {
// Floor 8 should be fire (wraps around)
expect(getFloorElement(8)).toBe('fire');
expect(getFloorElement(9)).toBe('water');
expect(getFloorElement(15)).toBe('fire'); // (15-1) % 7 = 0
expect(getFloorElement(16)).toBe('water'); // (16-1) % 7 = 1
});
});
});
@@ -463,7 +469,8 @@ describe('Spell Cost System', () => {
describe('Skill Definitions', () => {
it('all skills should have valid categories', () => {
const validCategories = ['mana', 'combat', 'study', 'craft', 'research', 'ascension'];
const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant',
'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'craft'];
Object.values(SKILLS_DEF).forEach(skill => {
expect(validCategories).toContain(skill.cat);
});
@@ -531,15 +538,16 @@ describe('Prestige Upgrades', () => {
// ─── Guardian Tests ──────────────────────────────────────────────────────
describe('Guardian Definitions', () => {
it('should have guardians every 10 floors', () => {
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100].forEach(floor => {
it('should have guardians on expected floors (no floor 70)', () => {
// Floor 70 was removed from the game
[10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => {
expect(GUARDIANS[floor]).toBeDefined();
});
});
it('should have increasing HP', () => {
let prevHP = 0;
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100].forEach(floor => {
[10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => {
expect(GUARDIANS[floor].hp).toBeGreaterThan(prevHP);
prevHP = GUARDIANS[floor].hp;
});