Adv 2.0 D&D Phase 12 E2e: !threat command

§14 surfaces the threat clock as a first-class command. !threat shows
level/100, current band, the per-band combat & supply effects (built
from ThreatBandInfo so display tracks any future band re-tuning), and
the last 5 ThreatEvent log entries with deltas + reasons. Adds a
"siege is active" / "past 70" footer when those gates have triggered.

Active-enemy camp guard and close-call evening recap stay deferred —
both need combat state inside an expedition, which doesn't exist yet
(expeditions still don't host their own combat loop). They land in the
combat-link phase along with the §8.1 combat-driven threat modifiers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-08 15:45:14 -07:00
parent f844068660
commit 9cb66d46ac
3 changed files with 115 additions and 0 deletions

View File

@@ -201,6 +201,49 @@ func TestApplyDailyThreatDrift_LogsApproachingSiegeOnceAt70(t *testing.T) {
}
}
func TestThreatBandEffectsBlock_Bands(t *testing.T) {
q := threatBandEffectsBlock(threatBandInfo(ThreatBandQuiet))
if !strings.Contains(strings.ToLower(q), "unaware") {
t.Errorf("Quiet block should mention unaware, got %q", q)
}
s := threatBandEffectsBlock(threatBandInfo(ThreatBandSiege))
if !strings.Contains(strings.ToLower(s), "short rests blocked") {
t.Errorf("Siege block should call out blocked short rests, got %q", s)
}
if !strings.Contains(s, "×2") {
t.Errorf("Siege block should show ×2 supply mult, got %q", s)
}
}
func TestHandleThreatCmd_NoExpeditionDoesNotPanic(t *testing.T) {
setupZoneRunTestDB(t)
uid := id.UserID("@threat-cmd-noexp:example")
defer cleanupExpeditions(uid)
p := &AdventurePlugin{}
if err := p.handleThreatCmd(MessageContext{Sender: uid}); err != nil {
t.Fatal(err)
}
}
func TestHandleThreatCmd_ActiveExpeditionLogsEvents(t *testing.T) {
setupZoneRunTestDB(t)
uid := id.UserID("@threat-cmd-active:example")
defer cleanupExpeditions(uid)
exp, err := startExpedition(uid, ZoneGoblinWarrens, "",
ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 1})
if err != nil {
t.Fatal(err)
}
if err := applyThreatDelta(exp.ID, 45, "test seed"); err != nil {
t.Fatal(err)
}
p := &AdventurePlugin{}
if err := p.handleThreatCmd(MessageContext{Sender: uid}); err != nil {
t.Fatal(err)
}
}
func TestApplyBossDefeatThreat_DropsLevel(t *testing.T) {
setupZoneRunTestDB(t)
uid := id.UserID("@exp-boss-threat:example")