mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
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>
128 lines
15 KiB
Go
128 lines
15 KiB
Go
// DO NOT REWRITE, SUMMARIZE, OR SHORTEN ANY ENTRIES IN THIS FILE
|
||
// zone_abyss_portal_flavor.go
|
||
// Tier 5 zone flavor — The Abyss Portal. 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.
|
||
//
|
||
// The canonical twinbee_gm_flavor.go ships BossEntryBelaxath (wired in
|
||
// dnd_zone_narration.go) but not RoomEntryAbyssPortal, so the room-entry
|
||
// pool lives here. This file also adds elite-room intros (Marilith), boss
|
||
// ability callouts for Belaxath the Undivided, and zone-specific lore.
|
||
|
||
package flavor
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// ROOM ENTRY — The Abyss Portal
|
||
// Defined here because no entry exists in twinbee_gm_flavor.go for this zone.
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
var RoomEntryAbyssPortal = []string{
|
||
"You step through what was a doorway and into a chamber where the geometry has stopped agreeing with itself. The far wall is closer than the near wall. The ceiling is in two places. I file this under 'Euclidean violation' and recommend not lingering on the question of which floor is the real floor.",
|
||
"The corridor breathes. Not the wind — the corridor. The walls expand and contract on a cycle that is approximately, but not exactly, a heartbeat. I match the rhythm involuntarily and dislike the experience.",
|
||
"A chamber where reality has a seam. The seam is visible. It runs floor-to-ceiling along the north wall and what is visible through the seam is not the next room. I do not look directly at it and recommend you adopt the same policy.",
|
||
"You enter what was once a chapel — to which god, you cannot tell, because the iconography has been overwritten in a script that hurts to read. The altar is intact. The thing on the altar is not what was placed there. I say nothing and move you past it.",
|
||
"The air pressure is wrong. Not low — wrong. There is a pressure that is not physical and it is leaning on you with the patience of a mountain. I identify it as psychic ambient and recommend moving briskly.",
|
||
"A staircase. The stairs go down on the way up and up on the way down. I do not pause to verify this. Takes the stairs in the intended direction and the direction works, mostly.",
|
||
"The corridor ahead is lit by something that is not fire. The light is red but the wrong red — the red of an alarm in a system that has never been fully implemented. I file this under 'demonic ambient' and shorten its stride.",
|
||
"You enter a room that has been used as a portal anchor. The anchor is still in place. The portal is not, but the place where it was is still warm. I track the residual energy and note the portal is not the only one — there are other warm spots, in other rooms, in a pattern.",
|
||
"A garden. Indoors. The plants are not plants. I identify them as 'demonic ornamentals — mid-tier, decorative, will react to perfumes' and recommend not perspiring near them.",
|
||
"The chamber ahead has been a courtroom and is now a kitchen. The transition was not deliberate. The transition was the result of someone with insufficient training opening a portal and not closing it. I note the timing must have been comedic and am not laughing.",
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// ELITE ROOM ENTRY — The Abyss Portal (Marilith)
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
var EliteRoomEntryAbyssPortal = []string{
|
||
"The chamber ahead is a barracks. Six racks of weapons along one wall, six saddles along another — except the saddles are wrong shape, and the weapons are six matched longswords, identical in length, identical in grip, identical in the subtle rune-work along the hilts. I identify the silhouette in the room before I identify the species and say: 'Marilith. Six longswords. Seven attacks a turn. Never stand directly in front.'",
|
||
"You enter a sparring gallery. The dummies are arranged in the kind of asymmetric pattern that suggests a six-armed practitioner. The practitioner is at the far end, mid-routine. She does not stop the routine. She incorporates you into it. I file this under 'Marilith — demonic captain, treats encounters as drills.'",
|
||
"A war room. Maps on the table, troop positions marked, a planning session in progress. The general at the head of the table has six arms and a snake's lower body and is not surprised by your arrival — was, in fact, expecting you on a different day, with different reinforcements, and is mildly disappointed by both. I say: 'Marilith. Parry on every attack. The fight is decided by who has more reactions.'",
|
||
"The corridor opens into a colonnade — twelve columns, six on each side, perfect symmetry. The figure between the central columns is a Marilith and she is using the symmetry — three swords engaged with the columns, three free. I note the geometry favors her and recommend fighting in the next room.",
|
||
"A throne room, smaller than the Belaxath one. The throne is occupied by a Marilith who is not sitting — she is coiled, three feet of snake below the seat, the rest of her vertical and ready. The longswords are all drawn. None of them are pointed at you. I track the angles and say: 'Reactive trait. She gets two reactions. Don't open with a counterspell-bait.'",
|
||
"You enter what is clearly a guard post. The Marilith on duty is not bored. She is patient in the particular way that someone who has been a soldier for fifteen hundred years is patient. The first three swords are already raised. The other three are coming up as you cross the threshold. I file this under 'professional courtesy' and ask who has Misty Step.",
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// BOSS ABILITY CALLOUTS — Belaxath the Undivided
|
||
// Used as a one-line cinematic suffix to BossEntryBelaxath when combat starts.
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
// Multiattack: Longsword + Whip.
|
||
var BelaxathMultiattackLines = []string{
|
||
"Longsword and whip every turn. The whip pulls. The longsword finishes. I say: 'Don't let the whip set the position. Reaction-cancel the pull if you've got it.'",
|
||
"Two attacks: one ranged-pull, one melee-finish. I file this under 'fighting-game grappler' and note the combo is fixed in order — survive the whip, survive the round.",
|
||
"He swings the longsword while the whip is already coiled around your front line. The bite is the second hit, every time, and it's the one calibrated for kills. I track the order and remind the party that the whip's not the kill — the whip's the setup.",
|
||
}
|
||
|
||
// Fire Aura: 10d6 fire to all melee attackers each turn; weapon attacks +3d6 fire.
|
||
var BelaxathAuraLines = []string{
|
||
"Stand within five feet and take ten-d-six fire at the start of his turn. His weapons add three-d-six fire on top. I say: 'Melee tax. Plan rotations. Don't camp a tile near him.'",
|
||
"Fire Aura. The room near him is a damage zone. I file this under 'positioning is HP' and recommend the back line do their job from the back line.",
|
||
"Anyone in melee takes ten-d-six fire on his upkeep regardless of whether he hits them. The fire ignores resistance later. I track the timer and remind the tank that being the wall is a budget item, not a strategy.",
|
||
}
|
||
|
||
// Lightning Discharge (recharge 5–6): 120-ft line, 12d6 lightning, DEX DC 20 half.
|
||
var BelaxathLightningLines = []string{
|
||
"One-twenty-foot line, twelve-d-six lightning, DEX DC 20 for half. Recharge five-or-six. I say: 'Don't line up. The line is the whole room if you're sloppy.'",
|
||
"Lightning Discharge. The line is long enough to clip the back rank from where the front rank is standing. I track the recharge die and shout the angle when the discharge points the wrong way.",
|
||
"Like the laser-eye boss in Contra, except the boss has six arms and the laser is on a recharge counter and the laser is sometimes lightning. I file this under 'know where the line is' and note the line is wherever you're easiest to hit.",
|
||
}
|
||
|
||
// Death Throes: on death, 30-ft radius, 20d6 fire, DEX DC 20 half — destroys non-legendary equipment on failed save.
|
||
var BelaxathDeathThroesLines = []string{
|
||
"He explodes when he dies. Twenty-d-six fire in a thirty-foot radius. DEX DC 20 for half. Failure also destroys non-legendary equipment on the wearer. I say: 'Drop him with range. Save your gear.'",
|
||
"Death Throes. The kill shot is the easy part. The Death Throes are the hard part. I file this under 'cost-of-victory mechanics' and recommend positioning before the kill, not after.",
|
||
"He goes out the way some bosses do — taking the room with him. Twenty-d-six on a save you might fail with disadvantage from the previous round's Frightened. I track the radius and remind the party that 'half damage' of twenty-d-six is still 'roll a new character.'",
|
||
}
|
||
|
||
// Demonic Resilience: resist cold/fire/lightning; immune to poison; immune to non-magical physical.
|
||
var BelaxathResilienceLines = []string{
|
||
"Resists cold, fire, lightning. Immune to poison. Immune to non-magical physical. I say: 'Magic damage only. Force, radiant, psychic — those carry. Mundane mace doesn't.'",
|
||
"Demonic Resilience. The standard demonic-lord package. I file this under 'check your damage type before you commit a slot' and note the Magic Resistance trait stacks on top — advantage on saves vs spells.",
|
||
"Three resistances and two immunities. The window for damage is narrow. I track party loadouts and remind the casters that radiant damage is not optional in this fight — it is the answer.",
|
||
}
|
||
|
||
// Phase 2 (<40% HP): grows to Huge size; advantage on all attacks; Death Throes recharge 4–6.
|
||
var BelaxathPhaseTwoLines = []string{
|
||
"Below forty percent he grows. Huge size, advantage on every attack, and Death Throes is now on a four-or-six recharge — meaning if you don't kill him cleanly, he might detonate in your face mid-fight. I say: 'Phase shift. Don't graze him below forty.'",
|
||
"Phase two: bigger, faster, and the explosion is no longer reserved for the kill. I track the new recharge and recommend every burst window the party can buy.",
|
||
"He grows. The room shrinks. Advantage on swings. The Death Throes timer becomes a real timer — if a recharge lands at the wrong moment, the room ends. I file this under 'fights you have to finish in two rounds or accept the consequences.'",
|
||
}
|
||
|
||
// BelaxathSignatureCallouts — combined pool for boss-entry suffix.
|
||
// Phase-two lines stay separate (surfaced via dedicated phase-two helper).
|
||
var BelaxathSignatureCallouts = func() []string {
|
||
out := make([]string, 0,
|
||
len(BelaxathMultiattackLines)+
|
||
len(BelaxathAuraLines)+
|
||
len(BelaxathLightningLines)+
|
||
len(BelaxathDeathThroesLines)+
|
||
len(BelaxathResilienceLines))
|
||
out = append(out, BelaxathMultiattackLines...)
|
||
out = append(out, BelaxathAuraLines...)
|
||
out = append(out, BelaxathLightningLines...)
|
||
out = append(out, BelaxathDeathThroesLines...)
|
||
out = append(out, BelaxathResilienceLines...)
|
||
return out
|
||
}()
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// LORE — The Abyss Portal
|
||
// Sampled by !lore inside this zone (zone-specific pool, generic fallback).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
var LoreLinesAbyssPortal = []string{
|
||
"The portal was not summoned. The portal was torn, from the other side, by Belaxath, with purpose, over thirty years. I note the duration matters: this was not a moment of carelessness on the part of a surface mage. This was a project, on the demonic side, executed with patience.",
|
||
"The site was a temple, before. Then a fortress, before. Then a meadow, before. The meadow is the original use of the land — the temple was built to seal something the meadow had been quiet over. I file this under 'land memory' and note the seal worked for fourteen centuries.",
|
||
"The Shard of the Abyss is the closing key. It is not optional. The portal cannot be closed without it, and the shard cannot be replaced — there is exactly one. I note the always-drop guarantee on the loot table is a story commitment, not a generosity.",
|
||
"The Marilith captains in the outer chambers are veterans of wars that did not happen on this plane. The wars happened in the Abyss, between demonic factions, over scheduling — which Layer got which incursion priority. I find this both bureaucratic and terrifying.",
|
||
"Belaxath is named 'the Undivided' because he refused to take a side in the Layer-Seven schism. The other Balors took sides. The other Balors are mostly dead. Belaxath remained intact and inherited their soldiers, in stages, over the schism's two-decade arc. I file this under 'patience as competitive advantage.'",
|
||
"The Vrock spores last for twenty-four hours after exposure. The save is the start of the timer, not the end. I suggest the party not assume the encounter is over when the room is clear — the spores keep ticking on the way to the next.",
|
||
"The Quasits in this zone are not random encounters. Each one has been individually deployed as a scout, with instructions, by a specific Hezrou. I note the chain of command is real and that killing a Quasit causes the Hezrou it reports to to know exactly where you are.",
|
||
"The portal is not the only opening. There are seven smaller openings in this zone — pinholes, nothing more — that Belaxath has been using to bring through reinforcements one at a time. The pinholes close when Belaxath dies. I file this under 'killing the boss is also a public service' and note the surface clergy will be relieved.",
|
||
}
|