mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Cancels the planned ARENA_BOSS_FLOW soak. The boss flow is shipped unconditionally with no legacy fallback — boss-flow errors now surface to the player and abort the round rather than silently falling back to the old CombatPower path. Deleted: - runArenaCombat (combat_bridge.go) — legacy CombatPower-vs-Lethality arena combat driver. - RenderCombatLogArena, renderArenaOutcome (combat_narrative.go) — legacy arena renderers. RenderCombatLogArena was a thin alias for RenderCombatLog; renderArenaOutcome was already dead. - renderArenaCombatFinalMessage (combat_bridge.go) — legacy "rewards + closer" trailing text. - arenaWinCloser, arenaLoseCloser (adventure_arena_combat.go) — flavor helpers that only fed the deleted renderers. File now just holds arenaParticipationXP. - arenaBossFlowEnabled + the ARENA_BOSS_FLOW env var (adventure_arena.go, .env.example). The env gate served only the now-removed fallback. - sendArenaCombatMessages (adventure_arena.go) — wrapper that switched pacing between boss flow and legacy. Replaced with direct sendZoneCombatMessages calls at the three call sites. - TestArenaBossFlowEnabled (bossflow test) and TestRenderCombatLogArena_ProducesPhaseMessages (narrative test). Simplified resolveArenaRound / resolveArenaSurvival / resolveArenaDeath: the bossNarr-vs-nil branches collapsed since narration is now always produced by resolveArenaBoss. Equipment degradation and the death-save reprieve hook are unchanged — both already worked off the boss-flow CombatResult. Migration doc (§4) updated to record the cancellation and the remaining tuning approach (playtest-driven, no flag soak window). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
172 lines
5.0 KiB
Go
172 lines
5.0 KiB
Go
package plugin
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRenderCombatLog_ProducesMessages(t *testing.T) {
|
|
player := Combatant{
|
|
Name: "Hero",
|
|
Stats: CombatStats{MaxHP: 100, Attack: 20, Defense: 10, Speed: 8, CritRate: 0.05},
|
|
Mods: CombatModifiers{DamageReduct: 1.0},
|
|
IsPlayer: true,
|
|
}
|
|
enemy := Combatant{
|
|
Name: "Goblin",
|
|
Stats: CombatStats{MaxHP: 60, Attack: 12, Defense: 5, Speed: 6, CritRate: 0.03},
|
|
Mods: CombatModifiers{DamageReduct: 1.0},
|
|
}
|
|
|
|
result := SimulateCombat(player, enemy, defaultCombatPhases)
|
|
msgs := RenderCombatLog(result, "Hero", "Goblin")
|
|
|
|
if len(msgs) < 1 {
|
|
t.Fatalf("expected at least 1 phase message, got %d", len(msgs))
|
|
}
|
|
}
|
|
|
|
func TestRenderCombatLog_PhaseHeaders(t *testing.T) {
|
|
player := Combatant{
|
|
Name: "Hero",
|
|
Stats: CombatStats{MaxHP: 100, Attack: 20, Defense: 10, Speed: 8, CritRate: 0.05},
|
|
Mods: CombatModifiers{DamageReduct: 1.0},
|
|
IsPlayer: true,
|
|
}
|
|
enemy := Combatant{
|
|
Name: "Rat",
|
|
Stats: CombatStats{MaxHP: 40, Attack: 8, Defense: 3, Speed: 4},
|
|
Mods: CombatModifiers{DamageReduct: 1.0},
|
|
}
|
|
|
|
result := SimulateCombat(player, enemy, defaultCombatPhases)
|
|
msgs := RenderCombatLog(result, "Hero", "Rat")
|
|
|
|
hasOpening := false
|
|
for _, m := range msgs {
|
|
if strings.Contains(m, "Opening") {
|
|
hasOpening = true
|
|
}
|
|
}
|
|
if !hasOpening {
|
|
t.Error("should have an Opening phase header")
|
|
}
|
|
}
|
|
|
|
func TestRenderCombatLog_WinPhaseMessages(t *testing.T) {
|
|
result := CombatResult{
|
|
PlayerWon: true,
|
|
PlayerStartHP: 100,
|
|
EnemyStartHP: 50,
|
|
PlayerEndHP: 80,
|
|
EnemyEndHP: 0,
|
|
TotalRounds: 3,
|
|
Closeness: 0.2,
|
|
Events: []CombatEvent{
|
|
{Round: 1, Phase: "opening", Actor: "player", Action: "hit", Damage: 25, EnemyHP: 25, PlayerHP: 100},
|
|
{Round: 2, Phase: "clash", Actor: "player", Action: "hit", Damage: 25, EnemyHP: 0, PlayerHP: 80},
|
|
},
|
|
}
|
|
|
|
msgs := RenderCombatLog(result, "Hero", "Slime")
|
|
|
|
if len(msgs) < 1 {
|
|
t.Fatalf("expected at least 1 phase message, got %d", len(msgs))
|
|
}
|
|
// Phase messages should contain damage numbers
|
|
found := false
|
|
for _, m := range msgs {
|
|
if strings.Contains(m, "25") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("phase messages should contain damage values")
|
|
}
|
|
}
|
|
|
|
func TestRenderCombatLog_LossPhaseMessages(t *testing.T) {
|
|
result := CombatResult{
|
|
PlayerWon: false,
|
|
PlayerStartHP: 100,
|
|
EnemyStartHP: 80,
|
|
PlayerEndHP: 0,
|
|
EnemyEndHP: 40,
|
|
TotalRounds: 4,
|
|
Closeness: 0.5,
|
|
Events: []CombatEvent{
|
|
{Round: 1, Phase: "opening", Actor: "enemy", Action: "hit", Damage: 50, PlayerHP: 50, EnemyHP: 80},
|
|
{Round: 2, Phase: "clash", Actor: "enemy", Action: "hit", Damage: 50, PlayerHP: 0, EnemyHP: 80},
|
|
},
|
|
}
|
|
|
|
msgs := RenderCombatLog(result, "Hero", "Dragon")
|
|
if len(msgs) < 1 {
|
|
t.Fatalf("expected at least 1 phase message, got %d", len(msgs))
|
|
}
|
|
}
|
|
|
|
func TestRenderCombatLog_ConsumableEvents(t *testing.T) {
|
|
result := CombatResult{
|
|
PlayerWon: true,
|
|
PlayerStartHP: 100,
|
|
EnemyStartHP: 60,
|
|
PlayerEndHP: 80,
|
|
EnemyEndHP: 0,
|
|
Events: []CombatEvent{
|
|
{Round: 0, Phase: "pre_combat", Actor: "consumable", Action: "use_consumable", Desc: "Goblin Grease"},
|
|
{Round: 0, Phase: "pre_combat", Actor: "consumable", Action: "flat_damage", Damage: 8, PlayerHP: 100, EnemyHP: 52},
|
|
{Round: 1, Phase: "opening", Actor: "player", Action: "hit", Damage: 30, PlayerHP: 100, EnemyHP: 22},
|
|
{Round: 2, Phase: "clash", Actor: "player", Action: "hit", Damage: 22, PlayerHP: 80, EnemyHP: 0},
|
|
},
|
|
}
|
|
|
|
msgs := RenderCombatLog(result, "Hero", "Imp")
|
|
|
|
found := false
|
|
for _, m := range msgs {
|
|
if strings.Contains(m, "Goblin Grease") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("should render consumable use event mentioning item name")
|
|
}
|
|
}
|
|
|
|
func TestRenderCombatLog_MonsterAbilityEvents(t *testing.T) {
|
|
result := CombatResult{
|
|
PlayerWon: true,
|
|
PlayerStartHP: 100,
|
|
EnemyStartHP: 60,
|
|
PlayerEndHP: 50,
|
|
EnemyEndHP: 0,
|
|
Events: []CombatEvent{
|
|
{Round: 1, Phase: "opening", Actor: "enemy", Action: "armor_break", Damage: 0, PlayerHP: 100, EnemyHP: 60},
|
|
{Round: 1, Phase: "opening", Actor: "player", Action: "hit", Damage: 30, PlayerHP: 100, EnemyHP: 30},
|
|
{Round: 2, Phase: "clash", Actor: "enemy", Action: "hit", Damage: 25, PlayerHP: 75, EnemyHP: 30},
|
|
{Round: 2, Phase: "clash", Actor: "enemy", Action: "poison_tick", Damage: 5, PlayerHP: 70, EnemyHP: 30},
|
|
{Round: 3, Phase: "decisive", Actor: "player", Action: "crit", Damage: 30, PlayerHP: 50, EnemyHP: 0},
|
|
},
|
|
}
|
|
|
|
msgs := RenderCombatLog(result, "Hero", "Wyrm")
|
|
|
|
hasArmorBreak := false
|
|
hasPoison := false
|
|
for _, m := range msgs {
|
|
if strings.Contains(m, "armor") || strings.Contains(m, "Armor") || strings.Contains(m, "defense") || strings.Contains(m, "Defense") {
|
|
hasArmorBreak = true
|
|
}
|
|
if strings.Contains(m, "poison") || strings.Contains(m, "Poison") || strings.Contains(m, "toxin") || strings.Contains(m, "Venom") || strings.Contains(m, "venom") {
|
|
hasPoison = true
|
|
}
|
|
}
|
|
if !hasArmorBreak {
|
|
t.Error("should render armor_break event")
|
|
}
|
|
if !hasPoison {
|
|
t.Error("should render poison_tick event")
|
|
}
|
|
}
|