Both T4 zones follow the D1-a/b/c pattern: extend the graph so the
longest entry→boss walk lands in band, keep the zone's authored
topology intact.
underdark (T4): 10 → 46 nodes, longest 30 (band 28-34)
feywild_crossing (T4): 9 → 53 nodes, longest 30 (band 28-34)
Topology preserved per zone:
- Underdark: 4-region authoring intact (every node carries a valid
RegionID); 3-way fork1 (drow R2 / illithid R3 / deep_chasm R1)
with LockNone / LockPerception / LockStatCheck CON; deep_chasm
intentionally stays R1; all three arms converge at the R4
throne_approach merge. Adds the missing Trap anchor (Collapsed
Arch) in the R1 preamble.
- Feywild Crossing: woven forks intact — hag_circle still has two
incoming edges (one per first-stage path), time_eddy stays
grove-exclusive, illusion_garden stays marsh-exclusive, fork1
remains CHA-vs-Perception with no LockNone option. Adds the
missing Trap anchor (Cursed Thicket) and a fae_court MERGE so
the three second-stage endings converge into one final boss
approach instead of triplicating it.
MinRooms/MaxRooms re-pitched 8-10 → 28-34 so the dice fallback also
lands in band for any future graphless variant.
T5 zones (dragons_lair, abyss_portal) deferred to D1-e — those
need a RegionID backfill on top of the length extension since their
regionsByZone entries are not currently honored by their graphs.
Both T3 zones follow the D1-a/b pattern: extend the graph so the longest
entry→boss walk lands in band, keep the zone's authored topology intact.
manor_blackspire (T3): 11 → 35 nodes, longest 23 (band 22-26)
underforge (T3): 10 → 31 nodes, longest 23 (band 22-26)
Topology preserved per zone:
- Manor: two-stacked-3-way forks identity + full lock-kind coverage
(LockNone, LockPerception, LockStatCheck, LockLevelMin). Adds the
missing Trap anchor (Cursed Threshold). All six spokes are 3 mid-nodes
so route choice is loot/encounter character, not shortcut economics.
- Underforge: one-way-descent gauntlet identity preserved — 17-node
linear preamble through the existing Cooling River (TRAP) and Magma
Chamber (ELITE), single 3-way antechamber fork before boss. All three
antechamber spokes are 4 mid-nodes.
MinRooms/MaxRooms re-pitched 7-9 → 22-26 so the dice fallback also lands
in band for any future graphless variant.
Three more zones follow the goblin_warrens pattern from D1-a: add the
missing Trap anchor and deepen the graph so the longest entry→boss walk
lands in its tier's target band.
crypt_valdris (T1): 8 → 17 nodes, longest 13 (band 12–14)
forest_shadows (T2): 9 → 19 nodes, longest 16 (band 16–20)
sunken_temple (T2): 10 → 26 nodes, longest 16 (band 16–20)
Topology preserved per zone:
- Crypt diamond + Perception-DC-15 secret off side_chapel.
- Forest asymmetric main fork (long branch carries the elite) +
WIS-DC-14 secret pre-boss.
- Sunken Temple sequential forks with no mid-path merge (four leaves
all terminate at the single boss room).
MinRooms/MaxRooms re-pitched to the §2 bands so the dice fallback also
lands in band for any future graphless zone.
Length is now sourced from the zone graph's longest entry→boss path,
not the dice roll, so "Room X/Y" matches what the player walks. The
dice fallback stays for graphless zones.
Goblin Warrens grows to the T1 12–14 band (13-node traversal): adds the
missing Trap anchor and deepens both fork branches + the post-merge
approach. Pattern reference for the remaining zones in D1-b…d.
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>
Phase 5-B's exit named two follow-ups: T3 below band (manor 39 /
underforge 47 vs 55-75) and the T4 spread (feywild 59 vs underdark
88, 30pp asymmetry). Phase 5-C re-runs the Phase 4-A trace on the
four affected zones under the shipped Phase 5-B cell and applies
roster knobs only (IsElite + SpawnWeight; no bestiary stat-block
touches).
TestExpeditionBalance_Phase5C_OutlierDiagnostic names the killer
per zone:
manor_blackspire (53%): Vampire Spawn (72% win, 42 kills) +
Revenant (16% win, 31 kills at SW=1, can't go lower). First
attempt trimmed VS SW 3 -> 2 and backfired by concentrating
elite share on Revenant (kills jumped to 48); reverted.
underforge (49.5%): Fire Elemental (57 kills, 61hp/win) +
Salamander (33 kills) carry the lethality; Helmed Horror only
100%-win elite but SW=1, no dilution effect.
feywild_crossing (54%): only 2 elites in the pool; Fomorian
(50% win, SW=1) still pulls 25% of elite picks and owns 65
kills.
underdark (86%): Drow at SW=7 fills nearly half of standard
rolls at 100% win / 1.1hp loss -- free-HP filler.
Roster changes:
manor Banshee promoted to elite SW=2 (was standard SW=3,
99.6% win). Soft 4th elite slot dilutes Revenant share
~14% -> ~11%. Standards collapse to Shadow+Poltergeist
(>=99% win).
underforge Helmed Horror SW 1 -> 3. Three-way elite pool drops
Fire Elemental's share from ~44% to ~33%.
feywild Green Hag promoted to elite SW=2 (was standard SW=4).
Adds soft 3rd elite + removes a 16hp/win standard.
Standards become Redcap+Will-o-Wisp+Quickling.
underdark Drow SW 7 -> 5 (light trim per the user's "lift
trailers, don't nerf leaders" stance). Standards
shift toward Hook Horror / Drow Mage.
Phase 1 matrix after Phase 5-C (200 trials, Fighter @ centerline):
T1 88.5% spread 1.0 (in band 70-90)
T2 74.5% spread 15.0 (in band 62-82)
T3 56.7% spread 0.5 (in band 55-75) +13.5pp
T4 77.0% spread 13.0 (over band 45-65, matches 5-B target;
spread halved from 25.5)
T5 58.0% spread 40.0 (abyss_portal 38% is the residual)
T3 mean lifted +13.5pp; both zones inside band with 0.5pp residual
spread. T4 spread halved (25.5 -> 13.0pp) by lifting feywild
+11.5pp and trimming underdark -4.5pp. T1/T2/T5 untouched.
No test debt -- no production tests pin SpawnWeight or IsElite on
the changed entries (TestMonsterKillTags_GatesKnownMonsters checks
vampire_spawn tags only, unaffected). -short suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4-A's per-monster trace named one or two miscategorised entries
on each of the four outlier zones — Phase 2c's single-boss-elite
dilution pattern hadn't been applied uniformly, and several over-tier
creatures were sitting on standard slots they couldn't carry.
Roster changes (no monster stat-block touches — bestiary is shared):
crypt_valdris (T1, 8.5% → 46.0%):
Dropped flameskull (CR3, 68 kills — still in underforge T3 where
its CR fits). Promoted specter to IsElite at SW=3 as the soft
dilutor, mirroring Phase 2c's worg/owlbear shape so the elite
bracket no longer auto-picks a one-shot.
forest_shadows (T2, 0.0% → 7.5%):
Standard pool was carrying two real killers — Displacer Beast
(38% win) and Bandit Captain (57% win, 14hp loss/win). Both
re-flagged IsElite; Owlbear SW cut 4→2 to keep the now-4-elite
pool diluted instead of front-loading Owlbear (9% win) at 50% of
elite rolls. Standards collapse to Dire Wolf + Dryad, matching
sunken_temple's "all standards ≥90% win" floor.
manor_blackspire (T3, 0.5% → 14.5%):
Wraith was the dominant standard killer (85 attributed kills,
45hp loss per win) — too punishing for the standard slot.
Promoted to IsElite; standard floor becomes
Shadow+Poltergeist+Banshee (all ≥92% win in trace).
abyss_portal (T5, 0.0% → 25.0%):
Nalfeshnee was a CR-13 demon on a T5 standard slot (2.8% win, 86
kills); promoted to IsElite. Second pass: Vrock at 86% win as
standard still bled 65 kills via 43hp/win attrition — promoted to
elite too. Standard collapses to Quasit alone (99.9% win, 1.3hp
loss), matching dragons_lair's near-zero standard-pool kill rate.
Tier-level effect on the Phase 3-B (b=50, f=tier) sweep:
T1: 26.8% → 44.0% (+17 pp)
T2: 6.2% → 10.0% (+4)
T3: 2.5% → 8.5% (+6)
T4: 7.8% → 7.8% (no T4 outlier identified)
T5: 27.5% → 41.0% (+14)
Outliers are no longer outliers (forest_shadows 7.5% vs sibling 13%
gap; abyss_portal 25% vs sibling 57.5%); the remaining T2/T3/T5
shortfall vs the target band (62-82%/54-74%/36-56%) is tier-wide —
both zones at each problem tier sit below band, so a per-zone tool
won't close it. Phase 4's exit condition ("all per-zone cells within
band") is therefore conditioned on a follow-up tier-wide pass, but
the per-zone bug-fix it was scoped to address is done.
-short suite: same two pre-existing failures
(TestAdv2Scenario_ZoneRunGoblinWarrens, TestMageSpellbookLineInRender).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each zone's elite pool was a single SpawnWeight=1 over-tier boss, so
InterruptElite bracket rolls auto-picked that boss 100% of the time.
The post-2b tier-lethality trace named these one-shots (Warchief, Hag,
Roper, Young Red Dragon) as the dominant remaining death source.
Promotes one mid-tier alt per zone to IsElite (underforge gets two
since fire_elemental was itself a non-elite killer). Boss SW=1 stays;
alt SW 3-4 gives the boss ~17-25% elite-roll share. pickZoneEnemy also
excludes IsElite from the standard pool, so promotion softens standard
encounters by one mid-difficulty entry — each zone still has 3-4
standard entries left. Crypt-Valdris already dual-elite, untouched.
Promotions: worg, owlbear, aboleth_thrall, vampire_spawn,
salamander+fire_elemental, drow_elite_warrior, night_hag,
dragonborn_cultist, hezrou.
Phase 1 matrix delta (200 trials/cell, Fighter, centerline):
goblin_warrens: 0% → 3.0% completion (first non-zero T1 reading)
sunken_temple: 0% → 0.5%
dragons_lair: death 100% → 60% (40% starve — different failure)
underdark: death 100% → 90% (10% starve)
feywild: death 100% → 92% (8% starve)
T3 cells still 100% death — next pass is global supply/threat levers.
Tier-lethality trace shows Drow Elite Warrior + Dragonborn Cultist
spawning and being winnable, with arcs stretching to 7-10 encounters
instead of fresh-entry one-shots.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ZoneArena to the ZoneID enum so the upcoming arena boss path can
route renderBossOutcome's twinBeeLine calls without inventing a parallel
flavor system. Synthetic by design — never registered via registerZone,
so !zone enter arena is unreachable and allZones() / registry tests are
unaffected.
Mood pools (Nat20/Nat1/BossDeath/PlayerDeath) are already zone-agnostic
in pickPool, so ZoneArena falls through to the generic defaults at zero
cost. Phase-two and boss-entry pools default to nil/generic too —
arena-specific overrides land alongside the bestiary work in step 3
only if existing flavor doesn't read right.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GM→DM rename across docs and code (GMNarrationType→DMNarrationType,
GMState→DMState, narration constants, comments) so the system reads as
"Dungeon Master" everywhere. Player-visible "GM mood" wording stays
where it appears in flavor.
Streamed zone/expedition combat: zone advance now stages patrol →
patrol play-by-play → patrol resolution → room intro → room play-by-play
→ final outcome through sendZoneCombatMessages with 2–3s pacing
(arena keeps its 5–8s window). Combat narrative lines pick up a compact
d20-vs-AC roll annotation for hit/crit/miss/block events.
Combat outcome polish: dndHPSnapshot lets narration show sheet HP
rather than legacy combat-engine HP, and markAdventureDead clears the
zombie state where hp_current was 0 but the legacy alive flag stayed
true after a D&D-layer KO.
Adv 2.0 announcement (ADVENTURE_2.0_ANNOUNCEMENT.md), README rewrite
covering the new layer, and adv2_scenario_test.go — a full
zone-run + expedition + harvest playthrough against a copy of the prod
DB asserting persisted state end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
High-priority fixes from the multi-agent audit of Adventure 2.0:
- Phase 11 nil-deref cluster: zoneOrFallback() helper replaces 8 unsafe
getZone(_) sites in dnd_zone_cmd.go. Corrupted zone IDs render a
placeholder instead of panicking.
- Briefing/recap idempotency: deliverBriefing/deliverRecap now claim
the rollover via a conditional UPDATE. Double-fires from clock skew
or restart become no-ops; supply burn and day++ no longer reapply.
- Graceful ticker shutdown: AdventurePlugin gains stopCh + Stop(); all
11 background tickers now select on stopCh in addition to ticker.C.
- Mood decay (§3.2): math.Round(elapsed*2) replaces int() truncation
so sub-hour gaps decay correctly.
- 24h auto-abandon (§4.3): getActiveZoneRun returns clean slate and
abandons stale runs whose LastActionAt is over 24h old.
- Respec / auto-migrate orphan cleanup: !respec and the auto-migrated
draft wipe now abandon active zone runs and expeditions before
deleting the dnd_character row.
- Phase R combat-link: applyBossDefeatThreat now wired from
resolveBossRoom (-20 threat); applyRoomCombatThreatForUser adds
+5/+8 from non-boss/elite kills (§8.1).
- Starvation → forced extraction: briefing-time check forces extract
with §10.2 coin tax when supplies hit zero.
- GMNat20/Nat1 narration wired into resolveCombatRoom and
resolveBossRoom (nat-20 takes precedence over nat-1).
- Treasure-undo race: LoadAndDelete on both timer-fire and `undo`
paths so only one side wins.
- Battle Master: Disarming, Menacing, Parry maneuvers added (3 → 6
of 10). Remaining 4 (Pushing, Goading, Riposte, Commander's Strike)
documented inline as needing ally/reaction mechanics the engine
doesn't model.
- Threat-70 warning: tracked in RegionState["siege_warning_fired"]
so a drop-and-recross doesn't re-fire the beat.
- region_state JSON decode error now logged via slog.Warn instead
of silently discarded.
Failing TestProdDB_DnDLayer fixed via option (a): track migrated
characters and only run round-trip / idempotency assertions on those,
skipping pre-existing prod-DB rows accumulated from live bot use.
New tests in dnd_audit_phase_R7_test.go cover: 24h auto-abandon,
briefing double-fire idempotency, threat-70 warning idempotency,
multi-region extract→resume state preservation, and starvation
forced-extraction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Underdark + Feywild Crossing (T4) and Dragon's Lair + Abyss Portal (T5)
registered. Zone hooks, atmosphere, factions, and boss descriptions
reuse the prewritten flavor in gogobee_dungeon_zones.md §5.
New bestiary entries:
Underdark: drow, drow_elite_warrior, drow_mage, mind_flayer,
hook_horror, roper, boss_ilvaras_xunyl
Feywild: redcap, will_o_wisp, quickling, night_hag, fomorian,
boss_thornmother (reuses Tier 2 green_hag)
Dragon's Lair: kobold, guard_drake, kobold_scale_sorcerer,
dragonborn_cultist, young_red_dragon, boss_infernax
Abyss Portal: quasit, vrock, hezrou, nalfeshnee, marilith,
boss_belaxath
Boss-entry pools (BossEntryInfernax, BossEntryBelaxath) and per-zone
RoomEntry pools for Underdark + Dragon's Lair already live in
twinbee_gm_flavor.go from a prior phase and are wired in
dnd_zone_narration.go — no narration switch changes here. Feywild and
Abyss room/boss/elite flavor lands in D5b.
Bumps TestBestiaryByCR cap from CR≤20 to CR≤30 to cover Infernax (CR
24, Ancient Red Dragon). New T4/T5 presence tests added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Haunted Manor of Blackspire + The Underforge registered. New bestiary
entries (poltergeist, banshee, wraith, vampire spawn, revenant,
Aldric Blackspire boss; magmin, azer, salamander, fire elemental,
helmed horror, Emberlord Thyrak boss) per dungeon zones doc §5.
Manor reuses Tier 1 shadow; Underforge reuses Tier 1 flameskull.
Manor Blackspire RoomEntry pool already lives in
twinbee_gm_flavor.go and is wired in dnd_zone_narration.go from a
prior phase, so no new flavor file is needed for that zone in D4a.
L1 tier-gate test bumped 4→6 now that T3 is visible to T1 players.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Forest of Shadows + Sunken Temple of Dar'eth registered. New bestiary
entries (dire wolf, bandit captain, owlbear, corrupted dryad, displacer
beast, green hag, kuo-toa + whip, water elemental, merrow, aboleth thrall,
Hollow King boss, Dreaming Aboleth boss) per dungeon zones doc §5. L1
tier-gate test expectation bumped from 2→4 zones now that T2 is visible
to T1 players.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zone Infrastructure SUB-A: ZoneDefinition / ZoneTier / ZoneEnemy /
ZoneBoss / ZoneLootEntry types and dndZoneRegistry. Tier 1 zones
populated per gogobee_dungeon_zones.md §5: Goblin Warrens (T1, L1-3,
boss Grol the Unbroken CR3) and Crypt of Valdris (T1, L1-3, boss
Valdris the Unburied CR5, phase 2 at 50% HP).
Helpers: getZone, zonesForLevel (2-tier-above ceiling), zonesByTier,
allZones.
Bestiary additions for Tier 1 enemy rosters: goblin_sneak,
goblin_archer, hobgoblin_grunt, worg, goblin_shaman,
hobgoblin_warchief, zombie, shadow, specter, wight, flameskull, plus
the two zone bosses. Zombie AC bumped 8->10 to satisfy engine
TestBestiaryAllWellFormed minimum.
7 new tests covering registry presence, bestiary-ref resolution, boss
CR floor, level-tier gating, loot-chance bounds, room-count sanity,
elite flag presence, and tier->level-range alignment. Full plugin
suite green.
Also marks SUB3 done in gogobee_subclass_system.md (all 15 subclasses
through L15 shipped across SUB3a-d).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>