Stone soaks show their work in the damage line

'Takes 2 damage' from a 3-point bolt read as broken arithmetic unless
you remembered the bloodstone. The damaged event now carries its soaks
(bloodstone's flat point, soulstone's spell-damage clamp) and the log
prints them: 'takes 2 damage (lightning-blast (reflected) — bloodstone
soaks 1)'. A blow the bloodstone drinks entirely — previously silent —
announces itself too. Events are derived, so replays gain the notes
retroactively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
Eric Wagoner
2026-08-18 00:02:07 -04:00
co-authored by Claude Fable 5
parent 1d7407cb20
commit 5243a173a8
2 changed files with 23 additions and 7 deletions
+16 -5
View File
@@ -501,8 +501,9 @@ export type GameEvent =
| { type: "attackAbsorbedIntoHand"; player: PlayerId; attackCard: CardInstance }
| { type: "attackMissed"; attacker: PlayerId; defender: PlayerId; attackCardId: string | null; because: "invisible" | "shrink" }
| { type: "attackResolved"; attacker: PlayerId; defender: PlayerId; attackCardId: string | null; damageDealt: number; reflectedDamage: number; fullyStopped: boolean; redirected: boolean }
| { type: "damaged"; player: PlayerId; amount: number; source: string; lifeAfter: number }
| { type: "damageImmune"; player: PlayerId; source: string; because: "medusa" }
| { type: "damaged"; player: PlayerId; amount: number; source: string; lifeAfter: number;
soaks?: { what: "bloodstone" | "soulstone"; amount: number }[] }
| { type: "damageImmune"; player: PlayerId; source: string; because: "medusa" | "bloodstone" }
| { type: "lifeGained"; player: PlayerId; amount: number; source: string; lifeAfter: number }
| { type: "stunned"; player: PlayerId; turnsLost: number }
| { type: "knockedBack"; player: PlayerId; from: Cell; to: Cell; squares: number }
@@ -5569,17 +5570,27 @@ function applyDamage(
}
// BLOODSTONE: "Lowers all damage done you by one point per attack."
const soaks: { what: "bloodstone" | "soulstone"; amount: number }[] = [];
if (displays(target, "bloodstone")) {
amount = Math.max(0, amount - 1);
if (amount === 0) return;
if (amount === 0) {
events.push({ type: "damageImmune", player: target.id, source, because: "bloodstone" });
return;
}
soaks.push({ what: "bloodstone", amount: 1 });
}
// SOULSTONE: "Last three points ... can only be lost to physical damage."
if (damageKind === "spell" && displays(target, "soulstone") && target.life > 3) {
amount = Math.min(amount, target.life - 3);
const clamped = Math.min(amount, target.life - 3);
if (clamped < amount) soaks.push({ what: "soulstone", amount: amount - clamped });
amount = clamped;
}
target.life -= amount;
events.push({ type: "damaged", player: target.id, amount, source, lifeAfter: target.life });
events.push({
type: "damaged", player: target.id, amount, source, lifeAfter: target.life,
...(soaks.length > 0 ? { soaks } : {}),
});
if (target.life > 0) return;
target.alive = false;