Combat: round half-up on HP wound persistence to fix 101→100 drift

Wound carry-over went through three integer truncations across the
legacy combat scale (123 max) and the dndChar scale (78 max). 101/123
persisted as int(64.04)=64, then restored as int(100.92)=100 — losing
0.92 HP every fight.

Switching both sides to math.Round means most HPs round-trip exactly;
the residual error is bounded by ±1 HP from the inherent scale mismatch
(1 dndChar HP ≈ 1.58 legacy HP, so some pairs collide on the smaller
scale). Acceptable; the prior always-truncate behavior was directionally
biased against the player.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-09 22:20:24 -07:00
parent c5217e9ecf
commit 9ce82f7c67

View File

@@ -2,6 +2,7 @@ package plugin
import ( import (
"log/slog" "log/slog"
"math"
"time" "time"
"gogobee/internal/db" "gogobee/internal/db"
@@ -429,7 +430,11 @@ func applyDnDHPScaling(stats *CombatStats, c *DnDCharacter) {
if pct < dndWoundFloor { if pct < dndWoundFloor {
pct = dndWoundFloor pct = dndWoundFloor
} }
scaled := int(float64(stats.MaxHP) * pct) // Round half-up. Naive int truncation loses up to 1 HP on every
// round-trip through the dndChar scale (which is coarser than the
// legacy combat scale): 101/123 → persist as 64/78 → restore as
// int(100.92) = 100, silently dropping a HP between fights.
scaled := int(math.Round(float64(stats.MaxHP) * pct))
if scaled < 1 { if scaled < 1 {
scaled = 1 scaled = 1
} }
@@ -470,7 +475,10 @@ func persistDnDHPAfterCombat(userID id.UserID, legacyStartHP, legacyEndHP int) {
pct = 1 pct = 1
} }
newHP := int(float64(c.HPMax) * pct) // Round half-up to mirror applyDnDHPScaling — a fight that ends at
// 101/123 should round-trip cleanly through the dndChar (78-scale)
// store and come back as 101, not 100.
newHP := int(math.Round(float64(c.HPMax) * pct))
if newHP < 0 { if newHP < 0 {
newHP = 0 newHP = 0
} }