fix: spire combat 11 high-severity discrepancies (issue #333)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

D-01: Implement per-weapon cast progress (weaponCastProgress record)
D-04: Bypass Executioner/Berserker discipline specials for golem attacks
D-09: Fix lightning counter direction (lightning→water, not lightning→earth)
D-10: Add full composite element counters (blackflame/radiantflames ↔ frost/water/light/dark)
D-15: Fix Executioner to check per-enemy HP < 25% instead of floorHP ratio
D-20: Fix dodge formula to match spec (min(0.55, floor × 0.003), starts at 0)
D-22: Fix shield modifier to use flat HP pool instead of percentage barrier
D-23: Wire up applyMageBarrierRecharge in the damage pipeline
D-25: Move guardian regen from per-damage-event to once-per-tick
D-26: Add guardian armor reduction to the guardian defensive pipeline
D-31: Fix armor_corrode to be temporary (restore armor on effect expiry)
D-38: Implement AoE damage distribution across enemies

All 1069 tests pass. No files exceed 400 lines.
This commit is contained in:
2026-06-08 18:25:05 +02:00
parent d07e74c396
commit 098ec86189
21 changed files with 203 additions and 75 deletions
+3 -1
View File
@@ -247,6 +247,8 @@ export const useGameStore = create<GameCoordinatorStore>()(
// Combat
if (ctx.combat.currentAction === 'climb') {
const combatCbs = buildCombatCallbacks({ ctx, effects, maxMana, addLog, useCombatStore, usePrestigeStore });
// Guardian regen once per tick (not per-damage-event, spec §5.3, D-25 fix)
combatCbs.applyGuardianRegen();
const roomEnemies = ctx.combat.currentRoom?.enemies ?? [];
const primaryEnemy = roomEnemies[0] ?? null;
const activeEnemy = combatCbs.applyMageBarrierRecharge(primaryEnemy) ?? primaryEnemy;
@@ -272,7 +274,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
rawMana = cr.rawMana; elements = cr.elements;
totalManaGathered += cr.totalManaGathered || 0;
if (cr.logMessages) cr.logMessages.forEach(msg => addLog(msg));
writes.combat = { ...(writes.combat || {}), currentFloor: cr.currentFloor, floorHP: cr.floorHP, floorMaxHP: cr.floorMaxHP, maxFloorReached: cr.maxFloorReached, castProgress: cr.castProgress, equipmentSpellStates: cr.equipmentSpellStates, golemancy: { ...(writes.combat?.golemancy || ctx.combat.golemancy), activeGolems: cr.activeGolems }, meleeSwordProgress: cr.meleeSwordProgress, currentRoom: cr.currentRoom };
writes.combat = { ...(writes.combat || {}), currentFloor: cr.currentFloor, floorHP: cr.floorHP, floorMaxHP: cr.floorMaxHP, maxFloorReached: cr.maxFloorReached, castProgress: cr.castProgress, weaponCastProgress: cr.weaponCastProgress, equipmentSpellStates: cr.equipmentSpellStates, golemancy: { ...(writes.combat?.golemancy || ctx.combat.golemancy), activeGolems: cr.activeGolems }, meleeSwordProgress: cr.meleeSwordProgress, currentRoom: cr.currentRoom } as Partial<CombatState>;
}
// Non-combat room tick (library, recovery, treasure, puzzle)