Files
gogobee/internal/flavor/zone_crypt_valdris_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

87 lines
8.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// DO NOT REWRITE, SUMMARIZE, OR SHORTEN ANY ENTRIES IN THIS FILE
// zone_crypt_valdris_flavor.go
// Tier 1 zone flavor — The Crypt of Valdris. 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 — Crypt of Valdris (Wight / Flameskull)
// ─────────────────────────────────────────────────────────────────────────────
var EliteRoomEntryCrypt = []string{
"The room is colder than the last one. Not by feeling — by measurement. I can tell. There is a presence here that does not believe in the ambient temperature of stone.",
"A figure sits in the cathedra at the chamber's far end. Robes intact. Skin not. The eyes open as you enter and they were already looking at the door. I draw breath despite not strictly needing to.",
"Floating above the altar: a skull, lit from within in a color that pretends to be fire. I recognize the pretense. The skull is doing math. The math is about where you are.",
"The torches in this room are out, but the room is lit anyway. I identify this as a problem worth naming aloud: 'That light has a source. The source is the encounter.'",
"You step into a chapel. Pews. Altar. A figure rising from the front pew with the unhurried grace of something that has been waiting in this exact pew for a very long time. I respect the patience and refuse to be impressed by it.",
"The mosaics on the walls show a procession. The procession leads to a king. The king is here. I note that the mosaics are still being added to in real time — one tile per visitor — and you have not been added yet.",
}
// ─────────────────────────────────────────────────────────────────────────────
// BOSS ABILITY CALLOUTS — Valdris the Unburied
// Used as a one-line cinematic suffix to BossEntryValdris when combat starts.
// ─────────────────────────────────────────────────────────────────────────────
// Corrupting Touch: +4d6 necrotic; target max HP reduced by damage dealt.
var ValdrisCorruptingTouchLines = []string{
"His hand passes through your chestplate without touching it. The damage isn't to the plate. I watch your max HP tick down and quietly stops watching.",
"Necrotic damage lands and lingers. The wound doesn't bleed — it forgets how. Your HP ceiling drops with it. I note: long rest restores. Survive to long rest.",
"Where Valdris touches, the body takes a piece of permanent. Not permanent-permanent. Long-rest-permanent. I consider this distinction the only good news in the sentence.",
}
// Legendary Resistance: 2/combat; auto-succeed one failed save.
var ValdrisLegendaryResistanceLines = []string{
"Your save would have landed. Valdris decides otherwise. The roll resets to a success on his side of the table. I note one charge spent, one remaining.",
"The spell hits cleanly and Valdris ignores it on principle. I mark the Legendary Resistance and remind you: he has two of these. After two, the math changes.",
"You rolled the number. The number was correct. Valdris waves it away with a gesture that costs him something but not enough. I keep count. Keep rolling.",
}
// Call of the Grave: recharge 56; summons 1d4 skeletons.
var ValdrisCallOfTheGraveLines = []string{
"Valdris speaks a word that is not for the living. Bones in the chamber walls remember they were once attached to people. I count the new combatants and recommend crowd control.",
"From the alcoves: the dry rattle of skeletons reassembling on demand. I note 1d4 new bodies on the field and quietly hopes for the lower end of that range.",
"The skeletons stand up like a Castlevania stage hazard — same animation, same timing, same exact bad news. I have memories of this and they are not warm ones.",
}
// Phase 2 (<50% HP): gains Fly speed; spells deal +1d6 necrotic.
var ValdrisPhaseTwoLines = []string{
"Valdris drops to half HP and stops walking. Not because he's tired — because he doesn't need to anymore. He rises a foot off the floor and I file this under 'phase shift.'",
"The fight changes shape. Valdris hangs in the air now, robes still, very calm, very lethal. His spells gain a necrotic edge. I say, simply: 'Phase two. Adjust.'",
"Half-HP is the line. You crossed it and Valdris crossed something else — gravity, mostly. The boss music gets a third instrument. I brace.",
"Like Castlevania's Death revealing his real attack pattern at half HP — the fight you started is not the fight you're finishing. I respect the pivot and dislike it intensely.",
}
// ValdrisSignatureCallouts — combined pool the boss-entry composer samples
// from at the start of the boss fight. Phase-two lines are surfaced via
// the dedicated phase-two helper rather than the entry suffix.
var ValdrisSignatureCallouts = func() []string {
out := make([]string, 0,
len(ValdrisCorruptingTouchLines)+
len(ValdrisLegendaryResistanceLines)+
len(ValdrisCallOfTheGraveLines))
out = append(out, ValdrisCorruptingTouchLines...)
out = append(out, ValdrisLegendaryResistanceLines...)
out = append(out, ValdrisCallOfTheGraveLines...)
return out
}()
// ─────────────────────────────────────────────────────────────────────────────
// LORE — Crypt of Valdris
// Sampled by !lore inside this zone (zone-specific pool, generic fallback).
// ─────────────────────────────────────────────────────────────────────────────
var LoreLinesCrypt = []string{
"Valdris was a scholar. Then he was an aspirant. Then he was a failed aspirant. The failure is the part that mattered — the lich ritual completed but completed wrong, and the wrongness has had three centuries to compound. I have read the contemporaneous accounts and find the original scholar likable, which makes the rest harder.",
"The phylactery shard you'll find here is one of seven. The other six are not in this crypt. I will not say where they are, because it doesn't fully know, and the partial knowledge it has is the kind that gets people followed by things that prefer not to be looked for.",
"The candles in the crypt do not consume wax. They do not consume time, either, in the strict sense. They were lit on the day Valdris was interred and they have been lit ever since. I respect this kind of consistency in a way that does not extend to approval.",
"The skeletons here are not all enemies. Some of them are former students, posed in the alcoves where they died, marked with the texts they were translating. I suggest you don't disturb the marked ones unless you mean to. Some lessons end and some lessons keep going.",
"The iron gate at the entrance has been opened from the inside three times in the last decade, and from the outside zero times. I find the asymmetry instructive. It also finds the pattern instructive: you are walking into the open door, not opening it.",
"In life, Valdris was patient. In undeath, Valdris is also patient — which is unfair, mathematically, because the same trait scales differently across mortality conditions. I dislike the math but respect the consistency.",
}