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>
This commit is contained in:
prosolis
2026-05-16 12:40:17 -07:00
parent f424300d87
commit 41adfce9f1
39 changed files with 1133 additions and 638 deletions

View File

@@ -21,14 +21,14 @@ package flavor
// ─────────────────────────────────────────────────────────────────────────────
var RoomEntrySunkenTemple = []string{
"You step into water that's been in this room for thirty years. The floor is tiled and slick. The pillars are barnacled at chest height — that's the old waterline. The new waterline is at your ankles. TwinBee notes the temple is partway through deciding which it prefers.",
"The chamber is half-flooded and half-lit. Salt in the air, salt on the walls, salt in places nothing oceanic should be reaching. TwinBee files this under 'unwell' and proceeds.",
"Glyphs cover the far wall in a script that doesn't match anything TwinBee recognizes. The angles are wrong on purpose. It keeps reading anyway, because it always tries, and stops when the reading starts to feel reciprocal.",
"The water in this room is not moving. Not the way still water doesn't move — the way a held breath doesn't move. TwinBee suggests not disturbing it more than necessary.",
"Pillars rise from water that goes deeper than the room should allow. TwinBee tests the depth with the haft of a polearm and stops at the point where the haft stops finding bottom.",
"A vaulted ceiling that's mostly intact. Water pools in places it shouldn't, drips from places it can't be coming from. TwinBee identifies the temple as 'wet on principle' and leaves it at that.",
"The mosaics on the floor are still legible under the water. They show a procession toward something that is decidedly not the god the temple was built for. TwinBee notes the mosaics are facing inward — they were laid for the thing inside, not for visitors.",
"You hear something move in the water. Not a splash — a displacement. The kind of displacement that happens when a large thing decides to be in a different part of the room than it was a moment ago. TwinBee acts unbothered and is bothered.",
"You step into water that's been in this room for thirty years. The floor is tiled and slick. The pillars are barnacled at chest height — that's the old waterline. The new waterline is at your ankles. I note the temple is partway through deciding which it prefers.",
"The chamber is half-flooded and half-lit. Salt in the air, salt on the walls, salt in places nothing oceanic should be reaching. I file this under 'unwell' and proceed.",
"Glyphs cover the far wall in a script that doesn't match anything I recognize. The angles are wrong on purpose. It keeps reading anyway, because it always tries, and stops when the reading starts to feel reciprocal.",
"The water in this room is not moving. Not the way still water doesn't move — the way a held breath doesn't move. I suggest not disturbing it more than necessary.",
"Pillars rise from water that goes deeper than the room should allow. I test the depth with the haft of a polearm and stop at the point where the haft stops finding bottom.",
"A vaulted ceiling that's mostly intact. Water pools in places it shouldn't, drips from places it can't be coming from. I identify the temple as 'wet on principle' and leave it at that.",
"The mosaics on the floor are still legible under the water. They show a procession toward something that is decidedly not the god the temple was built for. I note the mosaics are facing inward — they were laid for the thing inside, not for visitors.",
"You hear something move in the water. Not a splash — a displacement. The kind of displacement that happens when a large thing decides to be in a different part of the room than it was a moment ago. I act unbothered and am bothered.",
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -37,7 +37,7 @@ var RoomEntrySunkenTemple = []string{
// ─────────────────────────────────────────────────────────────────────────────
var BossEntryDreamingAboleth = []string{
"The chamber opens onto a pool that has no business being this large in a building this size. Something underneath the surface shifts, and the surface is suddenly the smallest part of what you're looking at. The Aboleth does not surface. It does not need to. Its mind arrives in your mind first, polite, ancient, deeply uninterested in your comfort. 'Welcome,' it says, and the word is in your voice. TwinBee says, very evenly, 'Don't agree to anything. Don't answer questions. Roll initiative.'",
"The chamber opens onto a pool that has no business being this large in a building this size. Something underneath the surface shifts, and the surface is suddenly the smallest part of what you're looking at. The Aboleth does not surface. It does not need to. Its mind arrives in your mind first, polite, ancient, deeply uninterested in your comfort. 'Welcome,' it says, and the word is in your voice. I say, very evenly, 'Don't agree to anything. Don't answer questions. Roll initiative.'",
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -45,12 +45,12 @@ var BossEntryDreamingAboleth = []string{
// ─────────────────────────────────────────────────────────────────────────────
var EliteRoomEntrySunkenTemple = []string{
"The water in the chamber ahead is moving. Not currents — purpose. It's gathering toward a center, taking on a shape, deciding what it wants its arms to look like this time. TwinBee says: 'Water elemental. The room is the enemy.'",
"You enter a hall where the floor is six inches of standing water and the standing water is, on closer inspection, watching you. A column of it rises and walks toward you with the deliberate gait of something that did not learn to walk from a creature with legs. TwinBee respects the originality.",
"The room hums at a frequency you feel in your teeth. The water rises into a vaguely humanoid pillar and the pillar steps forward. TwinBee notes this is the kind of fight where 'fall back to higher ground' is not a useful idea — there is no higher ground. The room is the ground.",
"A column of water becomes a hand becomes a torso becomes a thing intent on contact. TwinBee files water elementals under 'fights where lightning damage is fun' and asks who has lightning damage today.",
"The temple's oldest mechanism activates as you enter — a font in the ceiling drains, the floor floods another inch, and the new water gathers itself into a sentry. TwinBee says: 'It's been waiting since the day they built this. Be brief.'",
"Water rises in a vortex in the chamber's center. The vortex slows. The vortex opens what passes for eyes. TwinBee respects the staging and is not entertained by it.",
"The water in the chamber ahead is moving. Not currents — purpose. It's gathering toward a center, taking on a shape, deciding what it wants its arms to look like this time. I say: 'Water elemental. The room is the enemy.'",
"You enter a hall where the floor is six inches of standing water and the standing water is, on closer inspection, watching you. A column of it rises and walks toward you with the deliberate gait of something that did not learn to walk from a creature with legs. I respect the originality.",
"The room hums at a frequency you feel in your teeth. The water rises into a vaguely humanoid pillar and the pillar steps forward. I note this is the kind of fight where 'fall back to higher ground' is not a useful idea — there is no higher ground. The room is the ground.",
"A column of water becomes a hand becomes a torso becomes a thing intent on contact. I file water elementals under 'fights where lightning damage is fun' and ask who has lightning damage today.",
"The temple's oldest mechanism activates as you enter — a font in the ceiling drains, the floor floods another inch, and the new water gathers itself into a sentry. I say: 'It's been waiting since the day they built this. Be brief.'",
"Water rises in a vortex in the chamber's center. The vortex slows. The vortex opens what passes for eyes. I respect the staging and am not entertained by it.",
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -60,30 +60,30 @@ var EliteRoomEntrySunkenTemple = []string{
// Tentacle Multiattack: 3 hits; on-hit Diseased (no magical healing 24h until cured).
var AbolethTentacleMultiattackLines = []string{
"Three tentacles, three rolls. Each on-hit risks Diseased — no magical healing for 24 hours until cured. TwinBee says: 'Cleric's tools come back online tomorrow. Survive today.'",
"The Aboleth's tentacles arrive in sequence, three of them, the hits compounding. The disease isn't the damage — the disease is the design. Magical healing fails until you cleanse. TwinBee files this under 'durability problem.'",
"Three attacks, one turn. Any landing tentacle leaves a mark that locks out magical healing. TwinBee suggests potions, rest, and the kind of patience that pretends to be patience but is mostly grim arithmetic.",
"Three tentacles, three rolls. Each on-hit risks Diseased — no magical healing for 24 hours until cured. I say: 'Cleric's tools come back online tomorrow. Survive today.'",
"The Aboleth's tentacles arrive in sequence, three of them, the hits compounding. The disease isn't the damage — the disease is the design. Magical healing fails until you cleanse. I file this under 'durability problem.'",
"Three attacks, one turn. Any landing tentacle leaves a mark that locks out magical healing. I suggest potions, rest, and the kind of patience that pretends to be patience but am mostly grim arithmetic.",
}
// Enslave: recharge 6; WIS DC 14 or Charmed; player skips turn, drifts toward Aboleth.
var AbolethEnslaveLines = []string{
"The Aboleth speaks in your head and the speech is a question. WIS DC 14 or you spend your turn walking, peacefully, toward the water. TwinBee says: 'Recharge 6. It will try this again. Make the save.'",
"The Aboleth speaks in your head and the speech is a question. WIS DC 14 or you spend your turn walking, peacefully, toward the water. I say: 'Recharge 6. It will try this again. Make the save.'",
"Enslave. The word in your head sounds like your own thought. It isn't. WIS save now. On a fail, your turn becomes the Aboleth's turn and you become the wrong piece on the wrong side of the board.",
"Charm-class effect, but worse than charm — it's a directional pull. You drift toward the pool whether you want to or not. TwinBee shouts your save aloud so you don't mistake it for an idea you had.",
"Charm-class effect, but worse than charm — it's a directional pull. You drift toward the pool whether you want to or not. I shout your save aloud so you don't mistake it for an idea you had.",
}
// Mucus Cloud: melee attackers CON DC 14 or skin→membrane (6d6 acid if not submerged).
var AbolethMucusCloudLines = []string{
"Anyone in melee range gets a CON DC 14. Fail and your skin starts converting to a membrane that needs water to stay viable. Out of water it's 6d6 acid per turn. TwinBee says: 'Don't punch the fish.'",
"The Aboleth's mucus does something to skin that skin should not do. Membrane formation. Submerged is fine. Dry is the problem — 6d6 acid until it ends. TwinBee revises the engagement plan: ranged only.",
"Like the slime debuff in old console RPGs — except this one wants you to stay in the water and dissolves you if you don't. TwinBee respects the elegance of the threat and dislikes everything else about it.",
"Anyone in melee range gets a CON DC 14. Fail and your skin starts converting to a membrane that needs water to stay viable. Out of water it's 6d6 acid per turn. I say: 'Don't punch the fish.'",
"The Aboleth's mucus does something to skin that skin should not do. Membrane formation. Submerged is fine. Dry is the problem — 6d6 acid until it ends. I revise the engagement plan: ranged only.",
"Like the slime debuff in old console RPGs — except this one wants you to stay in the water and dissolves you if you don't. I respect the elegance of the threat and dislike everything else about it.",
}
// Legendary Actions (3/round): Detect / Tail Swipe (2 LA) / Psychic Drain (3 LA, max-HP cut).
var AbolethLegendaryActionLines = []string{
"The Aboleth gets three legendary actions per round. Detect (1), Tail Swipe (2), Psychic Drain (3, cuts max HP). TwinBee tracks the budget and reminds you when it spends.",
"Legendary Actions: 3 budget. Detect is cheap and reads your hand. Tail Swipe is medium and hurts. Psychic Drain is expensive and lasting — your max HP is the price. TwinBee suggests bursting it before it banks.",
"Three LA per round. The Aboleth will spend them on you and TwinBee will narrate each one with the specific resigned cadence of a GM who would prefer the boss had two LA.",
"The Aboleth gets three legendary actions per round. Detect (1), Tail Swipe (2), Psychic Drain (3, cuts max HP). I track the budget and remind you when it spends.",
"Legendary Actions: 3 budget. Detect is cheap and reads your hand. Tail Swipe is medium and hurts. Psychic Drain is expensive and lasting — your max HP is the price. I suggest bursting it before it banks.",
"Three LA per round. The Aboleth will spend them on you and I will narrate each one with the specific resigned cadence of a GM who would prefer the boss had two LA.",
}
// AbolethSignatureCallouts — combined pool for boss-entry suffix.
@@ -107,11 +107,11 @@ var AbolethSignatureCallouts = func() []string {
// ─────────────────────────────────────────────────────────────────────────────
var LoreLinesSunkenTemple = []string{
"Dar'eth was a sea god before the cult that built the temple decided he wasn't sea god enough and started worshipping something else under his roof. The temple kept the name. The thing being worshipped did not. TwinBee notes the bait-and-switch with professional appreciation.",
"The tide that withdrew thirty years ago didn't withdraw on its own. It was held back. By what — by whom — is not advertised, but TwinBee has seen the kind of pressure-line on the outer walls that suggests a decision was made and something is still making it.",
"Dar'eth was a sea god before the cult that built the temple decided he wasn't sea god enough and started worshipping something else under his roof. The temple kept the name. The thing being worshipped did not. I note the bait-and-switch with professional appreciation.",
"The tide that withdrew thirty years ago didn't withdraw on its own. It was held back. By what — by whom — is not advertised, but I have seen the kind of pressure-line on the outer walls that suggests a decision was made and something is still making it.",
"The Aboleth has been here longer than the temple has been a temple. The temple was built around the Aboleth. The cult thought they were containing it. The Aboleth thought they were furnishing the room. Both parties remained satisfied with the arrangement until the cult ran out of cultists.",
"The Kuo-toa here are not the local stock. They were brought. They are loyal in the way that a mind not entirely their own can be loyal. TwinBee considers their loyalty a symptom and the Aboleth the cause.",
"Aboleth memory is collective and ancient — it remembers things from before there were people to remember things. TwinBee finds this genuinely unsettling because the Aboleth's memory of you is identical to its memory of the cult and the temple's first architect: 'a thing that was here.' Past tense, even when you're standing right there.",
"The mucus residue in the upper chambers is recent. The Aboleth has been moving through the temple in the last few weeks — not just sitting in the central pool. Something has been making it restless. TwinBee suggests not being the thing that has been making it restless.",
"The phylactery shard you'll find here is not Valdris's. It belongs to a different lich entirely. TwinBee notes there are now two phylacteries in your itinerary and refuses to draw connecting lines on the map yet.",
"The Kuo-toa here are not the local stock. They were brought. They are loyal in the way that a mind not entirely their own can be loyal. I consider their loyalty a symptom and the Aboleth the cause.",
"Aboleth memory is collective and ancient — it remembers things from before there were people to remember things. I find this genuinely unsettling because the Aboleth's memory of you is identical to its memory of the cult and the temple's first architect: 'a thing that was here.' Past tense, even when you're standing right there.",
"The mucus residue in the upper chambers is recent. The Aboleth has been moving through the temple in the last few weeks — not just sitting in the central pool. Something has been making it restless. I suggest not being the thing that has been making it restless.",
"The phylactery shard you'll find here is not Valdris's. It belongs to a different lich entirely. I note there are now two phylacteries in your itinerary and refuse to draw connecting lines on the map yet.",
}