feat(golemancy): Phase 1 - Component-based construction system data definitions
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s

- Add new golem component types (Core, Frame, MindCircuit, Enchantment)
- Create 4 Core tiers, 7 Frames, 4 Mind Circuits, 8 Enchantments
- Rewrite golem utils for component-based stat computation
- Update GolemancyState with new fields (golemDesigns, golemLoadout, activeGolems)
- Update combat store, actions, and pipelines for new golem system
- Rewrite GolemancyTab with component selection UI
- Update fabricator discipline perks for new system
- Add comprehensive tests for component registries and utilities
- All files under 400 lines, all 743 tests passing
This commit is contained in:
2026-06-06 16:50:26 +02:00
parent c40e4ee940
commit 4b7aa82953
43 changed files with 2763 additions and 944 deletions
+64 -16
View File
@@ -1,19 +1,26 @@
// ─── Golem Combat Pipeline ─────────────────────────────────────────────────────
// Extracts golem combat setup from gameStore.ts tick()
// to keep the coordinator under the 400-line file limit.
// ─── Golem Combat Pipeline (Component-Based) ─────────────────────────────────
// Pipeline integration for the component-based golem combat system.
// Extracts golem combat setup from gameStore.ts tick() to keep the coordinator
// under the 400-line file limit.
import { useCombatStore } from '../combatStore';
import { useManaStore } from '../manaStore';
import { processGolemRoomDuration } from '../golem-combat-actions';
import { lowestHPEnemy } from '../combat-damage';
import type { ActiveGolem, EnemyState } from '../../types';
import {
summonGolemsOnRoomEntry,
processGolemMaintenance,
processGolemManaRegen,
processGolemAttacks,
countdownGolemRoomDuration,
} from '../golem-combat-actions';
import { useAttunementStore } from '../attunementStore';
import type { RuntimeActiveGolem } from '../../types';
export interface GolemCombatContext {
addLog: (msg: string) => void;
ctx: {
combat: {
currentFloor: number;
currentRoom: { roomType: string; unknown: Array<{ name: string }> };
currentRoom: { roomType: string; enemies: Array<{ name: string; hp: number; maxHP: number; armor: number }> };
};
prestige: { signedPacts: number[] };
};
@@ -22,20 +29,29 @@ export interface GolemCombatContext {
maxMana: number;
}
export interface GolemCombatResult {
export interface GolemCombatPipelineResult {
rawMana: number;
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
activeGolems: RuntimeActiveGolem[];
logMessages: string[];
}
/**
* Build the golem combat pipeline for the current tick.
* Returns golem state needed by processCombatTick.
*/
export function buildGolemCombatPipeline(_addLog: (msg: string) => void): {
activeGolems: ActiveGolem[];
activeGolems: RuntimeActiveGolem[];
golemDesigns: Record<string, { id: string; name: string; coreId: string; frameId: string; mindCircuitId: string; enchantmentIds: string[]; selectedManaTypes: string[]; selectedSpells: string[] }>;
golemApplyDamageToRoom: (dmg: number) => { floorHP: number; floorMaxHP: number; roomCleared: boolean };
} {
const activeGolems = useCombatStore.getState().golemancy?.activeGolems ?? [];
const combatState = useCombatStore.getState();
const golemancy = combatState.golemancy;
// New component-based active golems
const activeGolems = golemancy?.activeGolems ?? [];
// Reconstruct golem designs from store
const golemDesigns = golemancy?.golemDesigns ?? {};
const golemApplyDamageToRoom = (dmg: number) => {
const cs = useCombatStore.getState();
@@ -44,14 +60,19 @@ export function buildGolemCombatPipeline(_addLog: (msg: string) => void): {
return { floorHP: cs.floorHP, floorMaxHP: cs.floorMaxHP, roomCleared: false };
}
// Golems use focus-fire targeting (spec §9.4) — target lowest HP enemy
const target = lowestHPEnemy(room.enemies);
if (!target) {
// Focus-fire targeting: target lowest HP enemy
let target = room.enemies[0];
for (const e of room.enemies) {
if (e.hp > 0 && e.hp < (target?.hp ?? Infinity)) {
target = e;
}
}
if (!target || target.hp <= 0) {
return { floorHP: cs.floorHP, floorMaxHP: cs.floorMaxHP, roomCleared: false };
}
const updatedEnemies = room.enemies.map((enemy) => {
if (enemy.id === target.id && enemy.hp > 0) {
if (enemy.id === target!.id && enemy.hp > 0) {
return { ...enemy, hp: Math.max(0, enemy.hp - dmg) };
}
return enemy;
@@ -68,5 +89,32 @@ export function buildGolemCombatPipeline(_addLog: (msg: string) => void): {
return { floorHP: newFloorHP, floorMaxHP: cs.floorMaxHP, roomCleared: allDead };
};
return { activeGolems, golemApplyDamageToRoom };
return { activeGolems, golemDesigns, golemApplyDamageToRoom };
}
/**
* Process golem summoning on room entry.
*/
export function processGolemRoomEntry(
loadout: { enabled: boolean; designId: string; design: { name: string } }[],
currentFloor: number,
): {
rawMana: number;
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
activeGolems: RuntimeActiveGolem[];
logMessages: string[];
} {
const cs = useCombatStore.getState();
const attStore = useAttunementStore.getState();
const fabLevel = attStore.attunements?.fabricator?.level ?? 0;
const discBonus = 0; // TODO: compute from discipline
return summonGolemsOnRoomEntry(
loadout as any,
useManaStore.getState().rawMana,
useManaStore.getState().elements as any,
currentFloor,
cs.golemancy.activeGolems as any[],
discBonus,
);
}