fix: Restrict pact affinity cast speed bonus to invocation spells only
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
Previously (commit 62638d6), the pact affinity cast speed bonus was applied
to ALL spell casts (active, equipment, invocation). Per design intent, it
should only apply to invocation system spells.
Changes:
- invocation-system-spec.md: Update §6.2, §10.2, AC-13 to clarify bonus
applies to invocation spells only
- gameStore.ts: Stop computing pact affinity bonus into attackSpeedMult;
pass base 1.0 instead
- combat-actions.ts: Remove stale AC-13 comment
- combat-invocation.ts: Compute pact affinity cast speed bonus locally
for invocation spells only, using prestige upgrade level + discipline bonus
- invocation-utils.ts: Remove computeAttackSpeedMultFromPactAffinity (no
longer needed)
All 1235 tests pass.
This commit is contained in:
@@ -92,6 +92,7 @@ export function processCombatTick(
|
||||
bypassBarrier?: boolean,
|
||||
) => number,
|
||||
equippedSwords?: Record<string, EquipmentInstance>,
|
||||
pactAffinityUpgrade?: number,
|
||||
): CombatTickResult {
|
||||
const state = get();
|
||||
const logMessages: string[] = [];
|
||||
@@ -134,7 +135,6 @@ export function processCombatTick(
|
||||
// ─── Spell casting (only when a valid spell is configured) ────────────────
|
||||
if (spellDef) {
|
||||
const disciplineEffects = computeDisciplineEffects();
|
||||
// AC-13: Pact affinity cast speed bonus already applied to attackSpeedMult by gameStore
|
||||
const totalAttackSpeed = attackSpeedMult;
|
||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
|
||||
@@ -263,7 +263,7 @@ export function processCombatTick(
|
||||
|
||||
// ─── Invocation system (spec §10.1) ───────────────────────────────────
|
||||
const invResult = processInvocationTick(
|
||||
{ get, set, rawMana, elements, attackSpeedMult, signedPacts, currentRoom, floorHP },
|
||||
{ get, set, rawMana, elements, attackSpeedMult, signedPacts, currentRoom, floorHP, pactAffinityUpgrade: pactAffinityUpgrade || 0 },
|
||||
onFloorCleared,
|
||||
onDamageDealt,
|
||||
);
|
||||
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
selectInvocationSpell,
|
||||
deductInvocationSpellCost,
|
||||
computeChargeFillRate,
|
||||
computeCastSpeedBonus,
|
||||
computeCostMultiplier,
|
||||
computeDrainRateMultiplier,
|
||||
computeDrainPerTick,
|
||||
type ActiveInvocation,
|
||||
} from '../utils/invocation-utils';
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface InvocationTickParams {
|
||||
signedPacts: number[];
|
||||
currentRoom: FloorState;
|
||||
floorHP: number;
|
||||
pactAffinityUpgrade: number;
|
||||
}
|
||||
|
||||
export interface InvocationTickResult {
|
||||
@@ -53,7 +54,7 @@ export function processInvocationTick(
|
||||
modifiedDamage?: number;
|
||||
},
|
||||
): InvocationTickResult {
|
||||
const { get, set, rawMana: startRawMana, elements: startElements, attackSpeedMult, signedPacts, currentRoom, floorHP: startFloorHP } = params;
|
||||
const { get, set, rawMana: startRawMana, elements: startElements, attackSpeedMult, signedPacts, currentRoom, floorHP: startFloorHP, pactAffinityUpgrade } = params;
|
||||
let rawMana = startRawMana;
|
||||
let elements = startElements;
|
||||
let floorHP = startFloorHP;
|
||||
@@ -67,7 +68,7 @@ export function processInvocationTick(
|
||||
if (activeInvocation !== null) {
|
||||
// ── Invocation is active: drain charge and process cast ──
|
||||
const invResult = processActiveInvocation({
|
||||
get, set, rawMana, elements, attackSpeedMult, signedPacts,
|
||||
get, set, rawMana, elements, attackSpeedMult, signedPacts, pactAffinityUpgrade,
|
||||
currentFloor, floorHP, floorMaxHP, currentRoom: currentRoomState,
|
||||
invocationCharge, activeInvocation, logMessages,
|
||||
}, onFloorCleared, onDamageDealt);
|
||||
@@ -138,6 +139,7 @@ interface ActiveInvParams {
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
|
||||
attackSpeedMult: number;
|
||||
signedPacts: number[];
|
||||
pactAffinityUpgrade: number;
|
||||
currentFloor: number;
|
||||
floorHP: number;
|
||||
floorMaxHP: number;
|
||||
@@ -156,7 +158,7 @@ function processActiveInvocation(
|
||||
modifiedDamage?: number;
|
||||
},
|
||||
): InvocationTickResult {
|
||||
let { get, set, rawMana, elements, attackSpeedMult, signedPacts } = p;
|
||||
let { get, set, rawMana, elements, attackSpeedMult, signedPacts, pactAffinityUpgrade } = p;
|
||||
let { currentFloor, floorHP, floorMaxHP, currentRoom } = p;
|
||||
let { invocationCharge, activeInvocation } = p;
|
||||
const logMessages = p.logMessages;
|
||||
@@ -171,9 +173,13 @@ function processActiveInvocation(
|
||||
const drainPerTick = computeDrainPerTick(invSpellDef.cost.amount, drainMult);
|
||||
invocationCharge = Math.max(0, invocationCharge - drainPerTick);
|
||||
|
||||
// Cast progress uses attackSpeedMult which already includes pact affinity bonus (AC-12/AC-13)
|
||||
// AC-12/AC-13: Apply pact affinity cast speed bonus to invocation spells only
|
||||
// pactAffinityUpgrade is the prestige level (0-9), each level = +0.1; pactAffinityBonus comes from discipline
|
||||
const pactAffinity = pactAffinityUpgrade * 0.1 + (disciplineEffects.bonuses.pactAffinityBonus || 0);
|
||||
const castSpeedBonus = computeCastSpeedBonus(pactAffinity);
|
||||
const effectiveAttackSpeed = attackSpeedMult * (1 + castSpeedBonus);
|
||||
const invCastSpeed = invSpellDef.castSpeed || 1;
|
||||
const invProgressPerTick = HOURS_PER_TICK * invCastSpeed * attackSpeedMult;
|
||||
const invProgressPerTick = HOURS_PER_TICK * invCastSpeed * effectiveAttackSpeed;
|
||||
const newCastProgress = activeInvocation.castProgress + invProgressPerTick;
|
||||
|
||||
if (newCastProgress >= 1 && floorHP > 0) {
|
||||
|
||||
@@ -32,7 +32,7 @@ import type { TickContext, TickWrites } from './tick-pipeline';
|
||||
import type { GameCoordinatorState } from './gameStore.types';
|
||||
import type { EnemyState } from '../types';
|
||||
import { applyEnemyDefenses as applyEnemyDefensesFromPipeline } from './pipelines/combat-tick';
|
||||
import { computeAttackSpeedMultFromPactAffinity } from '../utils/invocation-utils';
|
||||
|
||||
|
||||
// Track paused conversions already logged to avoid flooding the activity log every tick
|
||||
const loggedPausedConversions = new Set<string>();
|
||||
@@ -290,11 +290,9 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
||||
}));
|
||||
useCombatStore.setState({ equipmentSpellStates });
|
||||
|
||||
// AC-12/AC-13: Pact affinity cast speed bonus (prestige + discipline)
|
||||
const attackSpeedMult = computeAttackSpeedMultFromPactAffinity(
|
||||
ctx.prestige.prestigeUpgrades.pactAffinity || 0,
|
||||
disciplineEffects.bonuses.pactAffinityBonus || 0,
|
||||
);
|
||||
// Pact affinity cast speed bonus only applies to invocation spells (AC-12/AC-13),
|
||||
// computed locally in combat-invocation.ts. Active/equipment spells use base 1.0.
|
||||
const attackSpeedMult = 1;
|
||||
const cr = useCombatStore.getState().processCombatTick(
|
||||
rawMana, elements, maxMana, attackSpeedMult,
|
||||
combatCbs.onFloorCleared,
|
||||
@@ -305,6 +303,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
||||
(dmg, enemy, roomType, addLogFn, bypassArmor, bypassBarrier) =>
|
||||
applyEnemyDefensesFromPipeline(dmg, enemy, roomType, addLogFn, bypassArmor, bypassBarrier),
|
||||
equippedSwords,
|
||||
ctx.prestige.prestigeUpgrades.pactAffinity || 0,
|
||||
);
|
||||
rawMana = cr.rawMana; elements = cr.elements;
|
||||
totalManaGathered += cr.totalManaGathered || 0;
|
||||
|
||||
@@ -198,21 +198,6 @@ export function computeDrainRateMultiplier(disciplineBonuses: DisciplineBonuses)
|
||||
return Math.max(MIN_DRAIN_MULTIPLIER, BASE_DRAIN_MULTIPLIER + reduction);
|
||||
}
|
||||
|
||||
// ─── Pact Affinity Attack Speed ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Compute the effective attack speed multiplier from pact affinity.
|
||||
* Combines prestige upgrade + discipline bonus, applies diminishing returns formula.
|
||||
* Used by gameStore to compute the attackSpeedMult passed to processCombatTick.
|
||||
*/
|
||||
export function computeAttackSpeedMultFromPactAffinity(
|
||||
pactAffinityUpgrade: number,
|
||||
pactAffinityBonus: number,
|
||||
): number {
|
||||
const total = pactAffinityUpgrade + pactAffinityBonus;
|
||||
return 1 * (1 + computeCastSpeedBonus(total));
|
||||
}
|
||||
|
||||
// ─── Drain Per Tick ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user