refactor: tick pipeline pattern — read all → compute all → write all (issue #103)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

- New tick-pipeline.ts: TickContext/TickWrites types + buildTickContext/applyTickWrites orchestrator
- gameStore.ts tick(): refactored to 3-phase pipeline (read snapshot → compute updates → batch writes)
- combat-actions.ts: accept signedPacts as parameter instead of usePrestigeStore.getState() in combat loop
- combatStore.ts/combat-state.types.ts: updated processCombatTick signature for signedPacts passthrough
- craftingStore.ts: removed tempState = { ...get(), rawMana } as any anti-pattern
- preparation-actions.ts: accept rawMana as explicit parameter instead of GameState bag
This commit is contained in:
2026-05-20 19:48:40 +02:00
parent ce084a61a3
commit ee893e8973
10 changed files with 317 additions and 109 deletions
+58 -10
View File
@@ -1,13 +1,25 @@
// ─── Combat Actions ─────────────────────────────────────────────────────────────
// Extracted combat logic from combatStore.ts
// Pure combat logic — no cross-store getState() calls.
// All external data (signedPacts, etc.) is passed in as parameters.
import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants';
import type { CombatState } from './combat-state.types';
import type { SpellState } from '../types';
import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils';
import { usePrestigeStore } from './prestigeStore';
import { computeDisciplineEffects } from '../effects/discipline-effects';
import { useDisciplineStore } from './discipline-slice';
export interface CombatTickResult {
rawMana: number;
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
logMessages: string[];
totalManaGathered: number;
currentFloor: number;
floorHP: number;
floorMaxHP: number;
maxFloorReached: number;
castProgress: number;
equipmentSpellStates: CombatState['equipmentSpellStates'];
}
export function processCombatTick(
get: () => CombatState,
@@ -22,19 +34,42 @@ export function processCombatTick(
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
modifiedDamage?: number;
},
) {
signedPacts: number[],
): CombatTickResult {
const state = get();
const logMessages: string[] = [];
let totalManaGathered = 0;
if (state.currentAction !== 'climb') {
return { rawMana, elements, logMessages, totalManaGathered };
return {
rawMana,
elements,
logMessages,
totalManaGathered,
currentFloor: state.currentFloor,
floorHP: state.floorHP,
floorMaxHP: state.floorMaxHP,
maxFloorReached: state.maxFloorReached,
castProgress: state.castProgress,
equipmentSpellStates: state.equipmentSpellStates,
};
}
const spellId = state.activeSpell;
const spellDef = SPELLS_DEF[spellId];
if (!spellDef) {
return { rawMana, elements, logMessages, totalManaGathered };
return {
rawMana,
elements,
logMessages,
totalManaGathered,
currentFloor: state.currentFloor,
floorHP: state.floorHP,
floorMaxHP: state.floorMaxHP,
maxFloorReached: state.maxFloorReached,
castProgress: state.castProgress,
equipmentSpellStates: state.equipmentSpellStates,
};
}
// Compute discipline bonuses once per tick
@@ -59,7 +94,7 @@ export function processCombatTick(
// Calculate base damage
const floorElement = getFloorElement(currentFloor);
const damage = calcDamage(
{ skills: {}, signedPacts: usePrestigeStore.getState().signedPacts },
{ skills: {}, signedPacts },
spellId,
floorElement,
disciplineEffects,
@@ -114,7 +149,7 @@ export function processCombatTick(
// Calculate damage
const eFloorElement = getFloorElement(currentFloor);
const eDamage = calcDamage(
{ skills: {}, signedPacts: usePrestigeStore.getState().signedPacts },
{ skills: {}, signedPacts },
eSpell.spellId,
eFloorElement,
disciplineEffects,
@@ -135,16 +170,29 @@ export function processCombatTick(
updatedEquipmentSpellStates[i] = { ...eSpell, castProgress: eCastProgress % 1 };
}
const newMaxFloorReached = Math.max(state.maxFloorReached, currentFloor);
set({
currentFloor,
floorHP,
floorMaxHP: getFloorMaxHP(currentFloor),
maxFloorReached: Math.max(state.maxFloorReached, currentFloor),
maxFloorReached: newMaxFloorReached,
castProgress,
equipmentSpellStates: updatedEquipmentSpellStates,
});
return { rawMana, elements, logMessages, totalManaGathered };
return {
rawMana,
elements,
logMessages,
totalManaGathered,
currentFloor,
floorHP,
floorMaxHP: getFloorMaxHP(currentFloor),
maxFloorReached: newMaxFloorReached,
castProgress,
equipmentSpellStates: updatedEquipmentSpellStates,
};
}
// Helper function to create initial spells