# Blood Energy Economy > **Status**: In Design > **Author**: SepComet + Claude > **Last Updated**: 2026-04-27 > **Implements Pillar**: 操作优于数值 (Skill Over Stats), 切换即承诺 (Switch is Commitment) ## Overview Blood Energy Economy 是夜裔的核心资源系统——管理玩家用于切换形态的"血能"资源。血能通过攻击敌人和承受伤害两种方式获取,仅用于支付形态切换的消耗。它不是"攒大招"的能量槽,而是一个强制性的战术节奏控制器:你必须在前线积极战斗来赚取血能,然后在正确的时机消费它来切换形态。没有血能系统,形态切换退化为"CD好了就切",失去"切换即承诺"的战术重量。 ## Player Fantasy 此系统是纯基础设施——玩家感知的是它赋能的操作(切形态),而非血能数字本身。不展开独立幻想章节。 ## Detailed Design ### Core Rules **1. Blood Energy Gain** Blood energy is earned through aggressive play: - **Attack Hit**: `baseAttackGain × enemyTypeMultiplier` per hit - **Enemy Kill**: `baseKillGain × enemyTypeMultiplier` per kill - **Taking Damage**: `baseHurtGain × (damageTaken / maxHealth)` — compensation ensures the player is never fully locked out of switching - **Wave Clear**: `fixedBonus × waveNumber` — reward for completing a wave **2. Blood Energy Spending** Blood energy has exactly one use: form switching. - **Switch Cost**: `baseSwitchCost × (1.0 - skillDiscount)` - **Interrupted Switch**: Blood energy is consumed on switch request (at Windup start). If the switch is interrupted, the energy is NOT refunded. This is the core punishment for switching at the wrong time. **3. Constraints** | Rule | Value | |------|-------| | Valid range | `[0.0, maxEnergy]` | | Initial value (game start) | `0` | | On death | Reset to `0` | | Maximum value | `baseMax × (1.0 + upgradeBonus)` | | Cannot exceed max | `Add()` clamps to max | | Cannot go below 0 | `Spend()` returns false if insufficient | ### States and Transitions Blood Energy Economy has no complex states — it is a single float value with three meaningful thresholds: | Threshold | Event | Significance | |-----------|-------|-------------| | `Current == 0` | `OnEnergyEmpty` | Player is locked out of switching — must attack or take damage | | `Current >= switchCost` | (check via `CanSpend()`) | Switching is possible | | `Current == Max` | `OnEnergyFull` | Further gains are wasted — signals "you should switch now" | Every value change fires `OnEnergyChanged(current, max)` for HUD/L1 consumption. ### Interactions with Other Systems | System | Direction | Interface | |--------|-----------|-----------| | Form Switch SM | Consumes | Calls `CanSpend(cost)` before switching, `Spend(cost)` on switch start | | HUD (L2) | Reads | Subscribes to `OnEnergyChanged` for display | | Skill Tree | Modifies | Can set `GainRate`, `baseSwitchCost`, `baseMax`, `skillDiscount` | | Death/Respawn | Resets | Calls `Reset()` on player death | ## Formulas ### Attack Gain ``` energyGain_attack = baseAttackGain × enemyTypeMultiplier ``` | Variable | Symbol | Type | Range | Description | |----------|--------|------|-------|-------------| | Base attack gain | `baseAttackGain` | float | 2–8 | Energy gained per hit | | Enemy type multiplier | `enemyTypeMultiplier` | float | 0.5–2.0 | Riskier enemies reward more | **Output**: 1–16 per hit | **Extreme**: Swarm enemies low (0.5x), Brute/Stalker high (1.5–2.0x) ### Kill Gain ``` energyGain_kill = baseKillGain × enemyTypeMultiplier ``` | Variable | Symbol | Type | Range | Description | |----------|--------|------|-------|-------------| | Base kill gain | `baseKillGain` | float | 10–25 | Kill reward significantly higher than hit | | Enemy type multiplier | `enemyTypeMultiplier` | float | 0.5–2.0 | Same as attack formula | **Output**: 5–50 | **Example**: Brute kill (2.0x) = 25 × 2.0 = 50 energy ### Hurt Gain ``` energyGain_hurt = baseHurtGain × (damageTaken / maxHealth) ``` | Variable | Symbol | Type | Range | Description | |----------|--------|------|-------|-------------| | Base hurt gain | `baseHurtGain` | float | 8–20 | Compensation to prevent deadlock | | Damage ratio | `damageTaken / maxHealth` | float | 0.0–1.0 | Bigger hits give more energy | **Intent**: Ensures pressured players can earn a switch-out resource. ### Switch Cost ``` switchCost = baseSwitchCost × (1.0 - skillDiscount) ``` | Variable | Symbol | Type | Range | Description | |----------|--------|------|-------|-------------| | Base switch cost | `baseSwitchCost` | float | 25–60 | ~5-12 attack hits to earn one switch (at default ratio ≈8) | | Skill discount | `skillDiscount` | float | 0.0–0.4 | Skill Tree cost reduction | **Output**: 18–50 | **Example**: baseSwitchCost=40, no discount → cost=40 ### Wave Clear Bonus ``` waveClearBonus = min(fixedBonus × waveNumber, 2 × fixedBonus) ``` | Variable | Symbol | Type | Range | Description | |----------|--------|------|-------|-------------| | Fixed bonus | `fixedBonus` | float | 20–40 | Base reward per cleared wave | | Wave number | `waveNumber` | int | 1–N | Caps at 2× fixedBonus to prevent late-wave overflow | **Output**: 20–80 | **Cap ensures** wave bonuses don't exceed ~80% of maxEnergy in one reward ### Max Energy ``` maxEnergy = baseMax × (1.0 + upgradeBonus) ``` | Variable | Symbol | Type | Range | Description | |----------|--------|------|-------|-------------| | Base max | `baseMax` | float | 80–150 | Initial energy cap (default 100) | | Upgrade bonus | `upgradeBonus` | float | 0.0–0.5 | Skill Tree capacity growth | **Output**: 100–150 ## Edge Cases | # | Condition | Resolution | Rationale | |---|-----------|------------|-----------| | 1 | `Add()` would exceed `MaxEnergy` | Clamp to Max, fire `OnEnergyFull` | Overflow waste — incentivizes spending before full | | 2 | `Spend()` amount exceeds current | Return `false`, no side effects, no events | Silent failure — UI should prevent this via CanSpend | | 3 | `Add()` called while dead | Ignore, return immediately | Dead players don't accumulate resources | | 4 | Switch interrupted (`Interrupt()`) | Energy NOT refunded — deducted at Windup start | Core punishment of "Switch is Commitment" | | 5 | `skillDiscount` pushes cost near 0 | Enforce minimum cost of `1.0` | Switching always has a cost | | 6 | Simultaneous Gain and Spend in same frame | Process Gains first, then Spends | Kill reward can enable immediate switch | | 7 | Wave bonus causes overflow | Clamp to Max (same as #1) | Incentivize spending before wave end | | 8 | `skillTree` undo reduces `maxEnergy` below `current` | Force `current = min(current, newMax)` | Cannot hold more than new cap | ## Dependencies | System | Relationship | Data Flow | |--------|-------------|-----------| | Form Switch SM | Downstream consumer | Calls `CanSpend()` / `Spend()` | | HUD (L2) | Downstream display | Subscribes to `OnEnergyChanged` | | Skill Tree | Upstream modifier | Sets `GainRate`, `baseMax`, `skillDiscount` | | Death/Respawn | Upstream reset | Calls `Reset()` | No upstream dependencies — this is a Foundation layer system. ## Tuning Knobs | Parameter | Default | Safe Range | Too Low | Too High | |-----------|---------|------------|---------|----------| | `baseAttackGain` | 5 | 2–8 | Switching too slow, stuck in one form | Switching too frequent, no tactical weight | | `baseKillGain` | 20 | 10–30 | Kills not worth prioritizing | Kill-chasing replaces form strategy | | `baseHurtGain` | 12 | 8–20 | Being pressured = no comeback | Deliberate self-damage becomes optimal | | `baseSwitchCost` | 40 | 25–60 | Switching has almost no threshold | Gap too long, player afraid to switch | | `baseMax` | 100 | 80–150 | Energy always capped → waste anxiety | Decision window too large → no urgency | | `fixedBonus` | 30 | 20–40 | Wave bonus imperceptible | Between-wave switching too lenient | **Key tuning pair**: `baseSwitchCost / baseAttackGain ≈ 8` — roughly 8 attack hits to earn one switch. This ratio defines the core combat rhythm. ## Visual/Audio Requirements This system has no direct visual output. Its presentation is via: - **HUD (L2)**: Blood energy bar with fill animation, drain animation on switch, color flash on value change - **VFX Spawner (L1)**: Brief player glow on `OnEnergyFull` — visual cue "switch now" - **Audio Player (L1)**: Warning sound on `OnEnergyEmpty` — audio cue "cannot switch" ## UI Requirements | Element | Position | Content | |---------|----------|---------| | Blood Energy Bar | HUD bottom/side | Amber Gold gradient, fill ratio `current / max` | | Form Switch Buttons | HUD corner | One per form: grey = insufficient energy, bright = can switch, highlighted = current | | Full Energy Indicator | Next to bar | Pulse effect when `OnEnergyFull` fires | ## Acceptance Criteria | # | GIVEN | WHEN | THEN | |---|-------|------|------| | 1 | Blood energy is 0 | Player attacks and hits an enemy | Energy increases by `baseAttackGain × enemyTypeMultiplier` | | 2 | Blood energy is 50, cost is 40 | Player requests a form switch | `CanSpend(40)` returns true, `Spend(40)` succeeds, energy = 10 | | 3 | Blood energy is 10, cost is 40 | Player requests a form switch | `CanSpend(40)` returns false, `Spend(40)` returns false, energy stays 10 | | 4 | Blood energy is 95, gain is 10 | Player gains energy | Energy becomes 100 (clamped), `OnEnergyFull` fires | | 5 | Energy was 60, 40 deducted on switch | Player is hit during Windup | Switch interrupted, energy stays at 20 (deducted cost not refunded, remainder preserved) | | 6 | Player dies | `Reset()` called | Energy becomes 0 | | 7 | Energy reaches 0 | Any cause | `OnEnergyEmpty` fires | | 8 | `skillDiscount`=0.4, `baseSwitchCost`=40 | Player switches | Cost = 24, energy decreased by 24 | | 9 | Energy changes (any cause) | Per frame or event | `OnEnergyChanged(current, max)` fires with correct values | | 10 | `baseMax` changed from 100 to 50, `current` is 80 | Skill tree update | Current clamps to 50 | | 11 | `skillDiscount` set to 1.0, `baseSwitchCost` = 40 | Player requests switch | Cost = max(40 × 0.0, 1.0) = 1.0 (minimum cost enforced) | | 12 | Player is dead, `Add(10)` called | Energy gain event | Gain ignored, energy unchanged | ## Open Questions - Final `baseSwitchCost` value (recommended 40) needs prototype validation — actual game feel will determine the right "attacks-per-switch" rhythm - Should there be a "low health → increased energy gain" comeback mechanic? Currently handled by hurt gain; revisit if playtest shows deadlock issues in low-health situations