chore: golemancy redesign cleanup — remove orphaned legacy code and update docs
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

This commit is contained in:
2026-06-07 12:54:12 +02:00
parent 59fe6cd111
commit 1a0886f702
13 changed files with 128 additions and 153 deletions
+81 -54
View File
@@ -414,68 +414,81 @@ At peak incursion (day 30), regen falls to 5% of base. Practical effects:
### 9.1 Overview
Golemancy is the **Fabricator attunement's** combat contribution. Golems are
summoned automatically at room entry, fight alongside the player, and disappear
after a fixed number of rooms or if their maintenance cost cannot be met.
Golemancy is the **Fabricator attunement's** combat contribution. Players design
custom golems from components (Core + Frame + Mind Circuit + Enchantments), then
configure a loadout. Golems are summoned automatically at room entry, fight alongside
the player, and disappear after a fixed number of rooms or if their maintenance cost
cannot be met.
### 9.2 Golem Loadout (Outside Spire)
The player configures a **golem loadout** from the Golemancy tab before entering
the spire. The loadout defines which golems to attempt to summon and in what order.
This configuration persists across rooms but not across spire runs.
the spire. The loadout defines which golem designs to attempt to summon and in what
order. This configuration persists across rooms but not across spire runs.
### 9.3 Summoning on Room Entry
When the player enters a new combat room:
When the player enters a new combat room, `summonGolemsOnRoomEntry()` iterates the
loadout in priority order:
```
onRoomEntry():
for each golem in golemLoadout:
if player has enough mana of golem.summonCostType >= golem.summonCost:
deductMana(golem.summonCost, golem.summonCostType)
summonGolemsOnRoomEntry(loadout, rawMana, elements, currentFloor, existingActiveGolems, disciplineSlotsBonus, fabricatorLevel):
for each entry in loadout:
if !entry.enabled → skip
if activeGolems.length >= totalSlots → break // max 7
if already active → skip
resolve components (Core, Frame, Mind Circuit) from design
stats = computeGolemStats(componentDesign)
if player can afford stats.totalSummonCost:
deduct summon cost from player mana
activeGolems.push({
...golemDef,
roomsRemaining: golemDef.maxRoomDuration,
designId: entry.designId,
summonedFloor: currentFloor,
attackProgress: 0,
roomsRemaining: stats.maxRoomDuration,
currentMana: stats.manaCapacity, // starts full
spellCastIndex: 0,
})
activityLog("${golem.name} summoned")
else:
activityLog("Not enough mana to summon ${golem.name} — skipped")
log "Not enough mana — skipped"
```
Total slots = `min(7, floor(fabricatorLevel / 2) + disciplineBonus)`.
Golems that could not be summoned (insufficient mana) are **not re-attempted**
within the same room. They will be attempted again on the next room entry.
### 9.4 Golem Combat
Each active golem attacks on its own `attackProgress` timer, identical to swords:
Each active golem attacks on its own `attackProgress` timer:
```
golemProgress += HOURS_PER_TICK × golem.attackSpeed
while golemProgress >= 1:
dmg = golem.baseDamage
// Apply golem's own elemental type if it has one
if golem.element:
dmg ×= getElementalBonus(golem.element, enemy.element)
// Apply golem special effects (DoT, armor pierce, AoE, etc.)
applyGolemEffects(golem, dmg, enemy)
applyDamageToRoom(dmg)
golemProgress -= 1
attackProgress += HOURS_PER_TICK × frame.attackSpeed
while attackProgress >= 1:
if mindCircuit has spells && golem.currentMana >= spellCost:
cast spell: damage = baseSpellDamage × frame.magicAffinity
golem.currentMana -= spellCost
spellCastIndex = (spellCastIndex + 1) % selectedSpells.length
else:
dmg = frame.baseDamage × (1 + frame.armorPierce)
apply enchantment effects (burn, slow, etc.)
applyDamageToRoom(dmg)
attackProgress -= 1
```
Golems ignore Executioner and Berserker discipline specials.
### 9.5 Maintenance Cost
Each tick, each active golem checks its maintenance cost:
Each tick, `processGolemMaintenance()` checks upkeep for each active golem:
```
tickGolemMaintenance(golem):
if player mana[golem.maintenanceCostType] >= golem.maintenanceCost × HOURS_PER_TICK:
deductMana(golem.maintenanceCost × HOURS_PER_TICK, golem.maintenanceCostType)
else:
dismiss(golem)
activityLog("${golem.name} dismissed — insufficient ${golem.maintenanceCostType} mana")
upkeepPerTick = core.manaRegen × 2 × HOURS_PER_TICK
if player has enough of core.primaryManaType:
deduct upkeepPerTick from player element mana
else:
dismiss(golem)
log "${name} dismissed — insufficient mana for upkeep"
```
A dismissed golem is **not re-summoned mid-room**. It will be re-attempted on the
@@ -483,13 +496,14 @@ next room entry if mana has recovered.
### 9.6 Room Duration Limit
`countdownGolemRoomDuration()` runs on room clear:
```
onRoomCleared():
for each activeGolem:
activeGolem.roomsRemaining -= 1
if activeGolem.roomsRemaining <= 0:
dismiss(golem)
activityLog("${golem.name} has faded after ${maxRoomDuration} rooms")
for each activeGolem:
golem.roomsRemaining -= 1
if golem.roomsRemaining <= 0:
dismiss(golem)
log "${name} has faded after ${maxRoomDuration} rooms"
```
Room duration ticks down on room clear, not on room entry — golems persist through
@@ -497,25 +511,38 @@ the full room they were summoned in.
### 9.7 Golem Data Shape
The runtime active golem type (`RuntimeActiveGolem` in `types/game.ts`):
```typescript
interface GolemDefinition {
id: string;
name: string;
tier: number; // 14 (determines general power)
baseDamage: number;
attackSpeed: number; // attacks per in-game hour
element?: ElementType; // optional elemental type for matchup
maxRoomDuration: number; // rooms before disappearing
summonCost: number;
summonCostType: ElementType | 'raw';
maintenanceCost: number; // per in-game hour
maintenanceCostType: ElementType | 'raw';
onHitEffect?: GolemHitEffect; // DoT, AoE, etc.
armorPierce?: number; // 0-1, bypasses this fraction of enemy armor
aoe?: boolean;
interface RuntimeActiveGolem {
designId: string; // Reference to the player's GolemDesign
summonedFloor: number; // Floor when golem was summoned
attackProgress: number; // Progress toward next attack (accumulated)
roomsRemaining: number; // Rooms before golem fades
currentMana: number; // Current mana in golem's own pool
spellCastIndex: number; // For alternating/cycling spell circuits
}
```
The serialized design type (`SerializedGolemDesign` in `types/game.ts`):
```typescript
interface SerializedGolemDesign {
id: string;
name: string;
coreId: string;
frameId: string;
mindCircuitId: string;
enchantmentIds: string[];
selectedManaTypes: string[];
selectedSpells: string[];
}
```
Golem stats are computed from components via `computeGolemStats()` in
`data/golems/utils.ts`, which sums summon costs from all components and derives
upkeep from `core.manaRegen × 2`.
---
## 10. In-Game Time Display
@@ -545,7 +572,7 @@ They are **in scope for the implementation this spec describes**:
| Mage barrier recharge | `MODIFIER_CONFIG.mage.barrierRechargeRate` | Data-only | Tick in `onDamageDealt` §5.2 |
| Guardian armor | `GuardianDef.armor` | Data-only | Add check to guardian pipeline §5.3 |
| DoT / debuff system | Spell/enchantment type defs | **Implemented**`dot-runtime.ts` complete and wired into combat tick; curse amplification added (issue #286) | Verified working |
| Golemancy combat | Full golem data exists | Disconnected | Implement per §9 |
| Golemancy combat | Full golem data + runtime | **Implemented** — component-based system complete | Verified working |
| Sword melee attacks | Weapon type exists | **Implemented** — meleeProgress with enemy defense application (issue #285) | Add `meleeProgress` per §3.1 |
| AoE target distribution | `SpellDefinition.aoe` flag | Partial | Implement per §3.2 |
| `elemMasteryBonus` | Stub in `calcDamage` | Hardcoded `1` | Future — leave as `1` for now |