Damage Resolution Pipeline
The damage resolution pipeline takes a raw attack and calculates the final damage after range falloff, resistances, and shield/armor/hull layers.
The Pipeline
``
Raw Damage → Range Modifier → Resistance → Layer Application → Final Result
``Range Falloff
Weapons have an optimal range and a falloff range. Damage is full within optimal, then decreases:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
library DamageCalculation {
/// @notice Calculate range-modified damage
/// @param baseDamage The weapon's base damage
/// @param distance Distance to target
/// @param optimalRange Full damage range
/// @param falloffRange Range over which damage drops to ~0
function applyRangeFalloff(
uint32 baseDamage,
uint32 distance,
uint32 optimalRange,
uint32 falloffRange
) internal pure returns (uint32) {
if (distance <= optimalRange) {
return baseDamage;
}
if (falloffRange == 0) {
return distance <= optimalRange ? baseDamage : 0;
}
uint32 excessRange = distance - optimalRange;
if (excessRange >= falloffRange * 3) {
return 0; // Beyond 3x falloff = no damage
}
// Linear falloff for simplicity
// Production systems might use exponential curves
uint32 falloffMultiplier = ((falloffRange 3 - excessRange) 10000)
/ (falloffRange * 3);
return (baseDamage * falloffMultiplier) / 10000;
}
/// @notice Apply resistance to damage
/// @param damage Pre-resistance damage
/// @param resistance Resistance in basis points (10000 = 100%)
function applyResistance(
uint32 damage,
uint16 resistance
) internal pure returns (uint32) {
if (resistance >= 10000) return 0;
return damage - ((damage * resistance) / 10000);
}
}Shield, Armor, Hull Layers
Damage flows through layers in order. Each layer absorbs what it can:
solidity
struct DamageResult {
uint32 shieldDamage;
uint32 armorDamage;
uint32 hullDamage;
bool destroyed;
}
function applyDamageToLayers(
uint32 damage,
uint32 shieldHp,
uint32 armorHp,
uint32 hullHp
) internal pure returns (DamageResult memory) {
DamageResult memory result;
uint32 remaining = damage;
// Shields absorb first
if (remaining <= shieldHp) {
result.shieldDamage = remaining;
return result;
}
result.shieldDamage = shieldHp;
remaining -= shieldHp;
// Then armor
if (remaining <= armorHp) {
result.armorDamage = remaining;
return result;
}
result.armorDamage = armorHp;
remaining -= armorHp;
// Finally hull
result.hullDamage = remaining > hullHp ? hullHp : remaining;
result.destroyed = remaining >= hullHp;
return result;
}Full Resolution
Putting it all together:
solidity
function resolveAttack(
uint256 attackerShipId,
uint8 weaponSlot,
uint256 targetShipId,
uint32 distance
) public returns (DamageResult memory) {
// 1. Get weapon config
WeaponConfig memory weapon = WeaponConfig.get(
getWeaponId(attackerShipId, weaponSlot)
);
// 2. Check cooldown
uint256 lastFired = WeaponCooldown.getLastFiredAt(
attackerShipId, weaponSlot
);
require(
block.timestamp >= lastFired + weapon.cooldownSeconds,
"Weapon on cooldown"
);
// 3. Apply range falloff
uint32 rangeDamage = DamageCalculation.applyRangeFalloff(
weapon.baseDamage, distance,
weapon.optimalRange, weapon.falloffRange
);
// 4. Get target resistances
ShipDefenses memory defenses = ShipDefenses.get(targetShipId);
uint16 resistance = getResistanceForType(
defenses, weapon.damageType
);
// 5. Apply resistance
uint32 finalDamage = DamageCalculation.applyResistance(
rangeDamage, resistance
);
// 6. Apply to layers
DamageResult memory result = applyDamageToLayers(
finalDamage,
defenses.shieldHp, defenses.armorHp, defenses.hullHp
);
// 7. Update state
WeaponCooldown.setLastFiredAt(
attackerShipId, weaponSlot, block.timestamp
);
updateShipHp(targetShipId, result);
return result;
}In the final chapter, we'll add events, logging, and integration patterns.
Sign in to track your progress.