fix: complete golemancy component-based redesign cleanup
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s

- Fix summonGolemsForRoom to use golemLoadout instead of removed enabledGolems
- Fix discBonus hardcoded to 0 in golem-combat.ts pipeline (now computed from discipline effects)
- Remove deprecated types: SummonedGolem, ActiveGolem, GolemDef
- Remove legacy GolemancyState fields: enabledGolems, summonedGolems, legacyActiveGolems
- Remove orphaned store actions: toggleGolem, setEnabledGolems
- Delete orphaned legacy data files: base-golems.ts, elemental-golems.ts, hybrid-golems.ts
- Update GolemDebugSection to use new golemLoadout system
- Update golemancy-spec.md §17 to reflect all features as complete
- Update all test files to match new type shapes
- All 958 tests passing
This commit is contained in:
2026-06-06 18:37:09 +02:00
parent bd15df85ff
commit 9d4b3f3c69
28 changed files with 89 additions and 443 deletions
@@ -36,7 +36,7 @@ function resetStores() {
roomResetState: {},
clearedRooms: {},
isDescentComplete: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
@@ -52,7 +52,7 @@ export function resetAllStores() {
roomResetState: {},
clearedRooms: {},
isDescentComplete: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
@@ -39,7 +39,7 @@ function resetStores() {
roomResetState: {},
clearedRooms: {},
isDescentComplete: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
@@ -38,7 +38,7 @@ function resetStores() {
roomResetState: {},
clearedRooms: {},
isDescentComplete: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
@@ -37,7 +37,7 @@ function resetStores() {
roomResetState: {},
clearedRooms: {},
isDescentComplete: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
@@ -18,7 +18,7 @@ function resetCombatStore() {
clearedFloors: {},
climbDirection: null,
isDescending: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
+1 -1
View File
@@ -29,7 +29,7 @@ function resetCombatStore() {
clearedFloors: {},
climbDirection: null,
isDescending: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
@@ -46,7 +46,7 @@ function resetAllStores() {
clearedFloors: {},
climbDirection: null,
isDescending: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
equipmentSpellStates: [],
comboHitCount: 0,
floorHitCount: 0,
-31
View File
@@ -1,31 +0,0 @@
// ─── Base Golem Definitions ───────────────────────────────────
import type { GolemDef } from './types';
import { elemCost } from './types';
export const BASE_GOLEMS: Record<string, GolemDef> = {
// ─── BASE GOLEMS ─────────────────────────────────────────────────────
// Earth Golem - Basic, available with Fabricator attunement
earthGolem: {
id: 'earthGolem',
name: 'Earth Golem',
description: 'A sturdy construct of stone and soil. Slow but powerful.',
baseManaType: 'earth',
summonCost: [elemCost('earth', 10)],
maintenanceCost: [elemCost('earth', 0.5)],
damage: 8,
attackSpeed: 1.5,
hp: 50,
armorPierce: 0.15,
isAoe: false,
aoeTargets: 1,
unlockCondition: {
type: 'attunement_level',
attunement: 'fabricator',
level: 2,
},
tier: 1,
maxRoomDuration: 3,
},
};
@@ -1,77 +0,0 @@
// ─── Elemental Variant Golems ───────────────────────────────────
import type { GolemDef } from './types';
import { elemCost } from './types';
export const ELEMENTAL_GOLEMS: Record<string, GolemDef> = {
// ─── ELEMENTAL VARIANT GOLEMS ────────────────────────────────────────
// Steel Golem - Metal mana variant
steelGolem: {
id: 'steelGolem',
name: 'Steel Golem',
description: 'Forged from metal, this golem has high armor piercing.',
baseManaType: 'metal',
summonCost: [elemCost('metal', 8), elemCost('earth', 5)],
maintenanceCost: [elemCost('metal', 0.6), elemCost('earth', 0.2)],
damage: 12,
attackSpeed: 1.2,
hp: 60,
armorPierce: 0.35,
isAoe: false,
aoeTargets: 1,
unlockCondition: {
type: 'mana_unlocked',
manaType: 'metal',
},
tier: 2,
maxRoomDuration: 3,
},
// Crystal Golem - Crystal mana variant
crystalGolem: {
id: 'crystalGolem',
name: 'Crystal Golem',
description: 'A prismatic construct that deals high damage with precision.',
baseManaType: 'crystal',
summonCost: [elemCost('crystal', 6), elemCost('earth', 3)],
maintenanceCost: [elemCost('crystal', 0.4), elemCost('earth', 0.2)],
damage: 18,
attackSpeed: 1.0,
hp: 40,
armorPierce: 0.25,
isAoe: false,
aoeTargets: 1,
unlockCondition: {
type: 'mana_unlocked',
manaType: 'crystal',
},
tier: 3,
maxRoomDuration: 4,
},
// Sand Golem - Sand mana variant
sandGolem: {
id: 'sandGolem',
name: 'Sand Golem',
description: 'A shifting construct of sand particles. Hits multiple enemies.',
baseManaType: 'sand',
summonCost: [elemCost('sand', 10), elemCost('earth', 4)],
maintenanceCost: [elemCost('sand', 0.6), elemCost('earth', 0.25)],
damage: 10,
attackSpeed: 2.0,
hp: 45,
armorPierce: 0.15,
isAoe: true,
aoeTargets: 2,
unlockCondition: {
type: 'mana_unlocked',
manaType: 'sand',
},
tier: 2,
maxRoomDuration: 3,
specialAbilities: [
{ name: 'Sandstorm', description: 'Hits multiple enemies (AoE)' },
],
},
};
-163
View File
@@ -1,163 +0,0 @@
// ─── Advanced Hybrid Golems ────────────────────────────────────
// Require Enchanter 5 + Fabricator 5
import type { GolemDef } from './types';
import { elemCost } from './types';
export const HYBRID_GOLEMS: Record<string, GolemDef> = {
// Lava Golem - Fire + Earth fusion
lavaGolem: {
id: 'lavaGolem',
name: 'Lava Golem',
description: 'Molten earth and fire combined. Burns enemies over time.',
baseManaType: 'earth',
summonCost: [elemCost('earth', 15), elemCost('fire', 12)],
maintenanceCost: [elemCost('earth', 0.6), elemCost('fire', 0.7)],
damage: 15,
attackSpeed: 1.0,
hp: 70,
armorPierce: 0.2,
isAoe: true,
aoeTargets: 2,
unlockCondition: {
type: 'dual_attunement',
attunements: ['enchanter', 'fabricator'],
levels: [5, 5],
},
tier: 3,
maxRoomDuration: 4,
specialAbilities: [
{ name: 'Burn', description: 'Burns enemies over time (DoT)' },
],
},
// Galvanic Golem - Metal + Lightning fusion
galvanicGolem: {
id: 'galvanicGolem',
name: 'Galvanic Golem',
description: 'A conductive metal construct charged with lightning. Extremely fast attacks.',
baseManaType: 'metal',
summonCost: [elemCost('metal', 12), elemCost('lightning', 8)],
maintenanceCost: [elemCost('metal', 0.4), elemCost('lightning', 0.7)],
damage: 10,
attackSpeed: 3.5,
hp: 45,
armorPierce: 0.45,
isAoe: false,
aoeTargets: 1,
unlockCondition: {
type: 'dual_attunement',
attunements: ['enchanter', 'fabricator'],
levels: [5, 5],
},
tier: 3,
maxRoomDuration: 4,
specialAbilities: [
{ name: 'Lightning Speed', description: 'Extremely fast attacks bonus' },
],
},
// Obsidian Golem - Dark + Earth fusion
obsidianGolem: {
id: 'obsidianGolem',
name: 'Obsidian Golem',
description: 'Volcanic glass animated by shadow. Devastating single-target damage.',
baseManaType: 'earth',
summonCost: [elemCost('earth', 18), elemCost('dark', 10)],
maintenanceCost: [elemCost('earth', 0.5), elemCost('dark', 0.6)],
damage: 25,
attackSpeed: 0.8,
hp: 55,
armorPierce: 0.5,
isAoe: false,
aoeTargets: 1,
unlockCondition: {
type: 'dual_attunement',
attunements: ['enchanter', 'fabricator'],
levels: [5, 5],
},
tier: 4,
maxRoomDuration: 5,
specialAbilities: [
{ name: 'Devastating Strike', description: 'Devastating single-target damage' },
],
},
// Prism Golem - Light + Crystal fusion
prismGolem: {
id: 'prismGolem',
name: 'Prism Golem',
description: 'A radiant crystal construct. Channels light into piercing beams.',
baseManaType: 'crystal',
summonCost: [elemCost('crystal', 16), elemCost('light', 10)],
maintenanceCost: [elemCost('crystal', 0.6), elemCost('light', 0.6)],
damage: 28,
attackSpeed: 2.0,
hp: 60,
armorPierce: 0.45,
isAoe: true,
aoeTargets: 3,
unlockCondition: {
type: 'dual_attunement',
attunements: ['enchanter', 'fabricator'],
levels: [5, 5],
},
tier: 4,
maxRoomDuration: 5,
specialAbilities: [
{ name: 'Piercing Beams', description: 'Channels light into piercing beams' },
],
},
// Quicksilver Golem - Water + Metal fusion
quicksilverGolem: {
id: 'quicksilverGolem',
name: 'Quicksilver Golem',
description: 'Liquid metal that flows around defenses. Fast and hard to dodge.',
baseManaType: 'metal',
summonCost: [elemCost('metal', 10), elemCost('water', 8)],
maintenanceCost: [elemCost('metal', 0.4), elemCost('water', 0.4)],
damage: 14,
attackSpeed: 4.0,
hp: 55,
armorPierce: 0.35,
isAoe: false,
aoeTargets: 1,
unlockCondition: {
type: 'dual_attunement',
attunements: ['enchanter', 'fabricator'],
levels: [5, 5],
},
tier: 3,
maxRoomDuration: 4,
specialAbilities: [
{ name: 'Flow', description: 'Flows around defenses, fast & hard to dodge' },
],
},
// Voidstone Golem - Void + Earth fusion (ultimate)
voidstoneGolem: {
id: 'voidstoneGolem',
name: 'Voidstone Golem',
description: 'Earth infused with void energy. The ultimate golem construct.',
baseManaType: 'earth',
summonCost: [elemCost('earth', 22), elemCost('void', 14)],
maintenanceCost: [elemCost('earth', 0.5), elemCost('void', 0.9)],
damage: 40,
attackSpeed: 0.6,
hp: 100,
armorPierce: 0.6,
isAoe: true,
aoeTargets: 3,
unlockCondition: {
type: 'dual_attunement',
attunements: ['enchanter', 'fabricator'],
levels: [5, 5],
},
tier: 4,
maxRoomDuration: 5,
specialAbilities: [
{ name: 'Void Infusion', description: 'Earth infused with void energy' },
],
},
};
+1 -2
View File
@@ -27,5 +27,4 @@ export { FRAMES, ALL_FRAMES } from './frames';
export { MIND_CIRCUITS, ALL_MIND_CIRCUITS } from './mindCircuits';
export { GOLEM_ENCHANTMENTS, ALL_GOLEM_ENCHANTMENTS } from './golemEnchantments';
// Legacy re-exports (deprecated, kept for migration)
export type { GolemDef } from './types';
-27
View File
@@ -149,31 +149,4 @@ export interface ActiveGolemV2 {
spellCastIndex: number;
}
// ─── Legacy Type (kept for backward compat during migration) ────────────
/** @deprecated Use GolemDesign instead */
export interface GolemDef {
id: string;
name: string;
description: string;
baseManaType: string;
summonCost: GolemManaCost[];
maintenanceCost: GolemManaCost[];
damage: number;
attackSpeed: number;
hp: number;
armorPierce: number;
isAoe: boolean;
aoeTargets: number;
unlockCondition: {
type: 'attunement_level' | 'mana_unlocked' | 'dual_attunement';
attunement?: string;
level?: number;
manaType?: string;
attunements?: string[];
levels?: number[];
};
tier: number;
maxRoomDuration: number;
specialAbilities?: { name: string; description: string }[];
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { getGuardianForFloor } from '../data/guardian-encounters';
import type { CombatStore, CombatState } from './combat-state.types';
import type { SpellState, EnemyState, EquipmentInstance, FloorState } from '../types';
import { applyOnHitEffect, processDoTPhase } from './dot-runtime';
import type { ActiveGolem, RuntimeActiveGolem } from '../types';
import type { RuntimeActiveGolem } from '../types';
import { getFloorMaxHP, getFloorElement, getMultiElementBonus, calcDamage, calcMeleeDamage, canAffordSpellCost, deductSpellCost } from '../utils';
import { computeDisciplineEffects } from '../effects/discipline-effects';
import {
@@ -142,16 +142,17 @@ export function advanceRoomOrFloor(get: GetFn, set: SetFn): void {
function summonGolemsForRoom(get: GetFn, set: SetFn): void {
const s = get();
const manaState = useManaStore.getState();
const enabledGolems = s.golemancy?.enabledGolems ?? [];
if (enabledGolems.length === 0) return;
const loadout = s.golemancy?.golemLoadout ?? [];
if (loadout.length === 0) return;
const currentActiveGolems = s.golemancy?.activeGolems ?? [];
const summonResult = summonGolemsOnRoomEntry(
enabledGolems,
loadout,
manaState.rawMana,
manaState.elements,
s.currentFloor,
currentActiveGolems,
0, // discBonus — computed in pipeline, not here
);
if (summonResult.logMessages.length > 0) {
@@ -244,7 +245,7 @@ export function createEnterSpireMode(get: GetFn, set: SetFn) {
roomResetState: {},
descentPeak: null,
isDescentComplete: false,
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
});
get().addActivityLog('floor_transition',
+1 -3
View File
@@ -1,7 +1,7 @@
// ─── Combat State Types ────────────────────────────────────────────────────────
// Shared types for combat store and combat actions to avoid circular dependency
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, ActiveGolem, RuntimeActiveGolem, EnemyState, EquipmentInstance, SerializedGolemDesign } from '../types';
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, RuntimeActiveGolem, EnemyState, EquipmentInstance, SerializedGolemDesign } from '../types';
/** Signature for the advanceRoomOrFloor callback to break circular dependency */
export type AdvanceRoomFn = (get: () => CombatStore, set: (s: Partial<CombatState>) => void) => void;
@@ -128,8 +128,6 @@ export interface CombatActions {
stayLongerInRoom: () => void;
// Golemancy
toggleGolem: (golemId: string) => void;
setEnabledGolems: (golemIds: string[]) => void;
addGolemDesign: (design: SerializedGolemDesign) => void;
removeGolemDesign: (designId: string) => void;
toggleGolemLoadoutEntry: (designId: string) => void;
+2 -28
View File
@@ -4,7 +4,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { createSafeStorage } from '../utils/safe-persist';
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, ActiveGolem, RuntimeActiveGolem, EnemyState, EquipmentInstance } from '../types';
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, RuntimeActiveGolem, EnemyState, EquipmentInstance } from '../types';
import { getFloorMaxHP } from '../utils';
import { generateSpireFloorState, getRoomsForFloor } from '../utils/spire-utils';
import { addActivityLogEntry } from '../utils/activity-log';
@@ -55,15 +55,10 @@ export const useCombatStore = create<CombatStore>()(
// Golemancy (component-based)
golemancy: {
// New component-based fields
golemDesigns: {},
golemLoadout: [],
activeGolems: [] as RuntimeActiveGolem[],
lastSummonFloor: 0,
// Legacy fields (deprecated)
enabledGolems: [],
summonedGolems: [],
legacyActiveGolems: [],
},
// Equipment spell states
@@ -204,7 +199,7 @@ export const useCombatStore = create<CombatStore>()(
currentRoomIndex: 0,
roomsPerFloor: 1,
maxFloorReached: Math.max(s.maxFloorReached, 1),
golemancy: { ...s.golemancy, activeGolems: [] as RuntimeActiveGolem[], summonedGolems: [], legacyActiveGolems: [] },
golemancy: { ...s.golemancy, activeGolems: [] as RuntimeActiveGolem[] },
};
});
},
@@ -224,27 +219,6 @@ export const useCombatStore = create<CombatStore>()(
stayLongerInRoom: () => stayLongerInRoom(get, set),
// Golemancy
toggleGolem: (golemId: string) => {
set((s) => {
const enabledGolems = s.golemancy?.enabledGolems || [];
const isEnabled = enabledGolems.includes(golemId);
return {
golemancy: {
...s.golemancy,
enabledGolems: isEnabled
? enabledGolems.filter(id => id !== golemId)
: [...enabledGolems, golemId]
},
};
});
},
setEnabledGolems: (golemIds: string[]) => {
set((s) => ({
golemancy: { ...s.golemancy, enabledGolems: golemIds },
}));
},
addGolemDesign: (d) => addGolemDesign(set, d),
removeGolemDesign: (id) => removeGolemDesign(set, id),
toggleGolemLoadoutEntry: (id) => toggleGolemLoadoutEntry(set, id),
@@ -13,6 +13,7 @@ import {
countdownGolemRoomDuration,
} from '../golem-combat-actions';
import { useAttunementStore } from '../attunementStore';
import { computeDisciplineEffects } from '../../effects/discipline-effects';
import type { RuntimeActiveGolem } from '../../types';
export interface GolemCombatContext {
@@ -107,7 +108,8 @@ export function processGolemRoomEntry(
const cs = useCombatStore.getState();
const attStore = useAttunementStore.getState();
const fabLevel = attStore.attunements?.fabricator?.level ?? 0;
const discBonus = 0; // TODO: compute from discipline
const discEffects = computeDisciplineEffects();
const discBonus = Math.floor(discEffects.bonuses.golemCapacity || 0);
return summonGolemsOnRoomEntry(
loadout as any,
-2
View File
@@ -49,8 +49,6 @@ export type {
GameAction,
ScheduleBlock,
StudyTarget,
SummonedGolem,
ActiveGolem,
GolemancyState,
GolemLoadoutEntry,
RuntimeActiveGolem,
-18
View File
@@ -144,19 +144,6 @@ export interface StudyTarget {
// ─── Golemancy Types ─────────────────────────────────────────────────────────
/** @deprecated Legacy type for predefined golems. Use GolemDesign instead. */
export interface SummonedGolem {
golemId: string;
summonedFloor: number;
attackProgress: number;
roomsRemaining: number;
}
/** @deprecated Legacy type. Use ActiveGolemV2 instead. */
export interface ActiveGolem extends SummonedGolem {
// attackProgress is inherited from SummonedGolem
}
/**
* Player-designed golem loadout entry.
* Each entry is a complete golem design (Core + Frame + Mind Circuit + Enchantments).
@@ -201,11 +188,6 @@ export interface GolemancyState {
activeGolems: RuntimeActiveGolem[];
/** Floor golems were last summoned on */
lastSummonFloor: number;
// Legacy fields kept for backward compatibility during migration
enabledGolems: string[];
summonedGolems: SummonedGolem[];
/** @deprecated Use activeGolems instead (RuntimeActiveGolem[]) */
legacyActiveGolems: ActiveGolem[];
}
-2
View File
@@ -40,8 +40,6 @@ export type {
GameAction,
ScheduleBlock,
StudyTarget,
SummonedGolem,
ActiveGolem,
GolemancyState,
GolemLoadoutEntry,
RuntimeActiveGolem,