Combat narration: format magnitude for stat_drain/debuff/ally_buff

These three stateful enemy-effect cases emitted their flavor pool via bare
pickRand, leaking the literal %d placeholder to players ("-%d damage on
every strike from here"). The magnitude is carried in CombatEvent.Damage,
exactly like the already-correct max_hp_drain / spell_fizzle cases; wrap
them in fmt.Sprintf. Add a regression test asserting no placeholder leaks.
This commit is contained in:
prosolis
2026-05-22 09:26:17 -07:00
parent 7dbfa0b56f
commit 2ce56cf76a
2 changed files with 27 additions and 3 deletions

View File

@@ -326,9 +326,9 @@ func renderEvent(e CombatEvent, playerName, enemyName string, result CombatResul
case "survive_at_1":
return pickRand(narrativeSurvive)
case "stat_drain":
return pickRand(narrativeStatDrain)
return fmt.Sprintf(pickRand(narrativeStatDrain), e.Damage)
case "debuff":
return pickRand(narrativeDebuff)
return fmt.Sprintf(pickRand(narrativeDebuff), e.Damage)
case "max_hp_drain":
return fmt.Sprintf(pickRand(narrativeMaxHPDrain), e.Damage)
@@ -346,7 +346,7 @@ func renderEvent(e CombatEvent, playerName, enemyName string, result CombatResul
case "fear_resist":
return pickRand(narrativeFearResist)
case "ally_buff":
return pickRand(narrativeAllyBuff)
return fmt.Sprintf(pickRand(narrativeAllyBuff), e.Damage)
case "timeout":
return pickRand(narrativeTimeout)

View File

@@ -0,0 +1,24 @@
package plugin
import (
"strings"
"testing"
)
// TestRenderEvent_StatefulDrainsFormatMagnitude is a regression test for the
// leaked "%d" in stateful enemy-effect narration. stat_drain, debuff and
// ally_buff carry their magnitude in CombatEvent.Damage and their flavor
// pools contain a %d placeholder; renderEvent must fmt.Sprintf them rather
// than emit the template raw (which surfaced "-%d damage" to players).
func TestRenderEvent_StatefulDrainsFormatMagnitude(t *testing.T) {
for _, action := range []string{"stat_drain", "debuff", "ally_buff"} {
e := CombatEvent{Actor: "enemy", Action: action, Damage: 4}
out := renderEvent(e, "Rurina", "Shadow", CombatResult{}, newActionPicker())
if strings.Contains(out, "%") {
t.Errorf("%s: leaked format placeholder in output: %q", action, out)
}
if !strings.Contains(out, "4") {
t.Errorf("%s: expected magnitude 4 in output: %q", action, out)
}
}
}