Files
gogobee/internal/flavor/zone_goblin_warrens_flavor.go
prosolis 41adfce9f1 Expedition autopilot Phase 4 + rogue sneak attack + TwinBee voice sweep
Tedium-removal pass driven by live play feedback. Three big threads:

Expedition autopilot Phase 4 — background auto-run + harvest-until-dry:
- New expeditionAutoRunTicker walks active expeditions every 15min
  (5min tick, per-expedition CAS on new last_autorun_at column). Skips
  combat sessions, briefing/recap quiet windows, expeditions <30min old.
- Walks up to 3 rooms/tick with compact narration; suppresses DMs when
  0 rooms walked or just hitting the per-tick cap (no "stretch complete"
  filler). Player only hears from the bot when a real decision is needed.
- Compact mode: one-line combat narration for trash/elite, auto-resolves
  elite doorways via the forward-sim engine (boss still pauses). Threaded
  via new advanceOnceWithOpts → resolveRoom → resolveCombatRoom.
- Auto-harvest now grinds each Common/Uncommon node until dry (cap at 8
  attempts/visit), mirroring manual !scavenge retries. Rare+ still pauses.
- Ambient ticker: anti-repeat by Kind via new last_ambient_kind column
  (avoids two pack_rat DMs in a row when the pool only has 6 lines).
- !expedition go <n> now routes to the fork-choice handler when active +
  numeric; fork footer rewritten to suggest !expedition go instead of
  !zone go. Boss/Elite doorway: formatNextRoomMessage routes the action
  hint by next room type and autopilot loop breaks via new nextRoomType
  field so "Room X/Y — Boss" doesn't double-print with contradictory
  hints (!zone advance vs !fight).
- runAutopilotWalk extracted from expeditionCmdRun so foreground and
  background share the loop body.

Rogue Sneak Attack actually exists now:
- Audit found the rogue's "Sneak Attack" passive was AutoCritFirst +
  5% damage rider. No Nd6, ever. Phase 2 Monte Carlo masked this
  with a small flat buff; the class's defining mechanic never matched
  its tooltip or 5e identity.
- Added SneakAttackDie int to CombatModifiers, per-hit Nd6 in
  combat_primitives.go (same lane as DivineStrikePerHit). Scales with
  level: 1d6 at L1-2 ... 4d6 at L7-8 ... capped at 10d6 at L19-20.
- AutoCritFirst + 5% rider retained as bonuses on top of working sneak
  attack — preserves the opener-burst feel.
- L7 rogue expected per-hit ~8 → ~22 damage. Valdris fight math goes
  from "16 rounds to kill, die in 8" to winnable.

TwinBee voice sweep (Phase B3):
- ~530 line changes across 24 files replacing third-person "TwinBee
  notes/files/tracks/respects/..." constructions with first-person
  inside flavor string literals. Code identifiers (TwinBeeLine,
  twinBeeLine, etc.), chat-prefix labels (🎭 **TwinBee:**), item names
  (TwinBee's Bell), achievements, and game-title references in
  fun.go (Konami TwinBee ship) left intact.
- Catches a real bug: Valdris fight rendered "TwinBee marks the
  Legendary Resistance" mid-combat in third person, contradicting
  the Phase B2 convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:40:17 -07:00

77 lines
6.9 KiB
Go

// DO NOT REWRITE, SUMMARIZE, OR SHORTEN ANY ENTRIES IN THIS FILE
// zone_goblin_warrens_flavor.go
// Tier 1 zone flavor — Goblin Warrens. Additive only. Pools sampled by
// internal/plugin via deterministic per-run, per-room hashing.
//
// Voice rules (from gogobee_dungeon_zones.md §3.3):
// • Third person for description; second person for outcomes.
// • Boss callouts get a beat of cinema. Don't overrun.
// • TwinBee references the right era — NES, SNES, arcade. Not modern.
package flavor
// ─────────────────────────────────────────────────────────────────────────────
// ELITE ROOM ENTRY — Goblin Warrens (Hobgoblin Warchief)
// ─────────────────────────────────────────────────────────────────────────────
var EliteRoomEntryWarrens = []string{
"The chamber widens. A hobgoblin in lacquered scale stands at the center, arms folded, watching the door. The lesser goblins go quiet. I note the silence is the part you should be paying attention to.",
"A war-banner hangs from the ceiling — three clans stitched together with rough thread. Beneath it, a Warchief turns to face you with the slow patience of someone who has done this part a hundred times. I straighten up out of habit.",
"The corridor opens into a drilling ground. Goblins in formation. A Hobgoblin barking orders that stop the moment your boot hits the threshold. I have seen this exact composition in Shining Force and find the parallel unhelpful.",
"You smell the polish before you see the armor. The Warchief's blade is oiled, his straps are tight, his stance is correct. I acknowledge, with reluctance, that this one was trained.",
"A circle of torches. A throne of stacked shields. The hobgoblin seated on it does not stand. He gestures, single-finger, in a way I universally translates as 'come here.' You go there.",
"The room has been cleared for combat. Rugs rolled, braziers spaced evenly, sand on the floor for grip. I respect the preparation and am professionally annoyed by it.",
}
// ─────────────────────────────────────────────────────────────────────────────
// BOSS ABILITY CALLOUTS — Grol the Unbroken
// Used as a one-line cinematic suffix to BossEntryGrol when combat starts.
// ─────────────────────────────────────────────────────────────────────────────
// Surprise Attack: +2d6 if player has not acted this combat.
var GrolSurpriseAttackLines = []string{
"Grol moves before you do. He always moves before you do. I note — too late — that the first hit in this fight is his.",
"The cleaver comes around in an arc that started before you were in the room. I wince in advance.",
"He's not waiting for the fight to start. The fight starts when he says it does. I mark the lesson and file it under 'next time.'",
}
// Heart of Hruggek: crits deal max damage (no roll).
var GrolHeartOfHruggekLines = []string{
"Grol crits and the dice don't roll. I have seen this Bugbear blessing once before. The number is the maximum number, every time, no negotiation.",
"His critical lands flat-max. No spread, no luck — just Hruggek's blessing collecting on a long-overdue debt. I note the damage with grim respect.",
"The crit hits like a Final Fantasy 'Berserk' status — capped, deterministic, unkind. I skip the roll and write the maximum.",
}
// Terrifying Roar: 1/combat; allies +2 to hit for 2 turns; player WIS DC 13 or Frightened.
var GrolTerrifyingRoarLines = []string{
"Grol throws his head back and roars. The room shakes. The goblins around him stand straighter. I say: 'WIS save. Now.'",
"The roar isn't a sound — it's a pressure change. Your knees know about it before your ears do. I respect the technique and ask you to roll Wisdom.",
"He bellows once. The torchlight bends. The goblins answer with cheers that sound rehearsed. I note the buff is up; you have two turns to outlast it.",
}
// GrolSignatureCallouts — combined pool the boss-entry composer samples
// from. Concrete ability pools above are kept distinct so future per-turn
// ability hooks (D6) can wire to a specific trigger.
var GrolSignatureCallouts = func() []string {
out := make([]string, 0,
len(GrolSurpriseAttackLines)+len(GrolHeartOfHruggekLines)+len(GrolTerrifyingRoarLines))
out = append(out, GrolSurpriseAttackLines...)
out = append(out, GrolHeartOfHruggekLines...)
out = append(out, GrolTerrifyingRoarLines...)
return out
}()
// ─────────────────────────────────────────────────────────────────────────────
// LORE — Goblin Warrens
// Sampled by !lore inside this zone (zone-specific pool, generic fallback).
// ─────────────────────────────────────────────────────────────────────────────
var LoreLinesWarrens = []string{
"The Warrens are old. Not goblin-old — older. The tunnels predate the goblins and the goblins know it. They've widened doorways meant for something taller and braced ceilings meant for something heavier. I have theories about what was here first. None of them are reassuring.",
"Grol united the three clans by killing each of their war-chiefs in single combat, in order, in one afternoon. The fourth clan declined to send a chief and pledged loyalty by letter. I find the letter, professionally, very funny.",
"The graffiti on the walls dates the occupation. Goblins date by leaders and the names go back six generations. I can read goblin script when pressed and note that the third-oldest name is one a paladin order would still recognize.",
"The Merchant's Road above doesn't know about the Warrens. The Merchant's Road above used to know about the Warrens. I was here when it stopped knowing, and note that the people in charge of Knowing About Things were the first to disappear.",
"Hobgoblins hold formation; goblins hold grudges; bugbears hold pretty much anything they pick up. I offer this not as a joke but as a tactical primer that will save you a lot of HP if you remember it.",
"The belt Grol wears is not a trophy. It is a contract. Each tooth on it is a debt some troll forgave when the wearing of the belt began, and the debts run in both directions. I strongly suggests not breaking the belt unless you mean to.",
}