New additive flavor files (zone_manor_blackspire_flavor.go,
zone_underforge_flavor.go) with elite-room intros, boss ability
callouts, and zone lore. Manor reuses RoomEntryHauntedManor from
twinbee_gm_flavor.go; Underforge had no canonical RoomEntry/BossEntry
pools yet, so both are defined in the new zone file.
Aldric Blackspire: Multiattack (life drain) / Charm / Children of the
Night / Mist Form / Phase Two
Emberlord Thyrak: Molten Strike / Forge Breath / Living Forge /
Construct Resilience / Phase Two
Wires zoneRoomEntryPool, bossEntryPool, zoneLorePool,
bossSignaturePool, and eliteRoomEntryPool to the new tier-3 pools.
Adds Tier 3 routing tests for boss-entry, elite line, lore, and
room-entry overlays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tier3TrapCatalog (Antimagic Field, Necrotic Miasma, Symbol of
Death, Planar Rift) per design doc §6 Tier 3-5 table. Tiers 3, 4,
and 5 share this pool — the doc lumps them together. Antimagic
Field uses a new SuppressesMagic flag (no damage, narrative
suppression beat); the other three deliver dice damage and skip
the unmodelled secondary effects (max-HP debuff, drop-to-0,
teleport) for a later phase.
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>
New additive flavor files (zone_forest_shadows_flavor.go,
zone_sunken_temple_flavor.go) with elite-room intros, boss ability
callouts, and zone lore. Reuses existing RoomEntryForestShadows and
BossEntryHollowKing from twinbee_gm_flavor.go. The Sunken Temple had
no canonical RoomEntry/BossEntry pools yet, so both are defined in the
new zone file.
Hollow King: Corrupting Aura / Root Surge / Devour Light / Phase Two
Dreaming Aboleth: Tentacle Multiattack / Enslave / Mucus Cloud /
Legendary Actions
Wires zoneRoomEntryPool, bossEntryPool, zoneLorePool,
bossSignaturePool, and eliteRoomEntryPool to the new tier-2 pools.
Updates the D2b "no callout pool" test to use Dragon's Lair (still
unflavored) and adds Tier 2 routing tests for boss-entry, elite line,
lore, and room-entry overlays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Flame Jet (3d6 fire), Collapsing Ceiling (4d6 bludgeoning), and
Glyph of Warding (5d8 lightning) per design doc §6 Tier 2. Detect DCs
match the table (Perception/Investigation/Arcana 14). Save mechanics
("DEX DC X half") are not modeled — full dice resolve on a failed
detect, matching the Tier 1 convention from D2a.
trapsByTier now dispatches Apprentice → tier2TrapCatalog. Added
deterministic-pick + variety + range tests for the new tier.
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>
New additive flavor files (zone_goblin_warrens_flavor.go,
zone_crypt_valdris_flavor.go) with elite-room intros, boss ability
callouts (Grol: Surprise Attack / Heart of Hruggek / Terrifying Roar;
Valdris: Corrupting Touch / Legendary Resistance / Call of the Grave /
Phase Two), and zone-specific lore deep-dives. Existing
twinbee_gm_flavor.go is untouched per the protected-files contract.
Wires three narration helpers in dnd_zone_narration.go:
composeBossEntry — boss-entry render plus a one-line ability callout
eliteRoomEntryLine — elite-room atmospheric prefix
zoneLorePool — zone-specific !lore pool with generic fallback
Boss-entry callsite in dnd_zone_cmd.go now uses composeBossEntry; the
elite branch of resolveCombatRoom prefixes with the zone's elite line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the flat-percent HP nick from D1e with the design doc's three
Tier 1 traps (Pit, Tripwire Alarm, Poison Dart). Each trap defines its
own detect skill + DC and damage dice; selection is deterministic per
(runID, roomIdx).
resolveTrapRoom now picks a trap, rolls Perception/Investigation, and
either narrates a clean spot (zero damage) or rolls the trap's dice and
applies HP loss with the existing KO-protection clamp. Tripwire deals
no damage but raises the alarm narratively.
Higher-tier zones still fall through to resolveTrapRoomLegacy until
D3a/D4a fill in their catalogs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires real combat into !zone advance. Each room now resolves through
its own path: Entry is pure flavor, Exploration spawns a SpawnWeight-
biased non-elite from the zone roster, Elite filters to elite-flagged
entries, Trap nicks 8–20% MaxHP scaled by tier (KO-protected), Boss
runs the bestiary entry from zone.Boss with the zone Loot table on
victory. Combat reuses the existing dungeonCombatPhases pipeline with
the player's full D&D layer (class/race/subclass passives, equipment,
HP scaling, armed abilities, pending casts) and persists HP, subclass
state, and CR-weighted XP after each kill.
Mood event triggers fold in: nat-20s/nat-1s scanned from CombatResult
events apply +3/-2 deltas, player_death applies -5 + abandons the run,
zone_complete (already wired in D1d) lands when the boss falls. Loot
drops materialize into adventure_inventory with coin patterns
("coins_2d10x5") expanded into rolled gold-pouch treasure rows and
named items rendered as tier-scaled placeholder treasure (real
equipment-registry wiring is a later content phase).
Updates the D1d mood-on-completion test to drive the persistence layer
directly, since real combat against the L1 Goblin Warrens roster is
non-deterministic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the existing internal/flavor TwinBee GM pools into the !zone
state machine. Adds GMNarrationType, mood band derivation, the §3.2
mood-event delta table, and ±2/hr passive decay toward 50. Zone enter
narrates the entry room; advance narrates each subsequent room (boss
rooms use the named BossEntry* pools, completion uses ZoneComplete
and applies MoodEventZoneComplete). Line selection is deterministic
on (runID, roomIdx) so repeat reads render the same prose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dnd_zone_cmd.go ships handleDnDZoneCmd with subdispatch over the D1b
state machine: list, enter <id|#|name>, status, map, advance, abandon.
Bare !zone (or !zone list) shows zones available at the player's level
with !zone enter hints; an active run is flagged inline so the player
can't lose track of it.
Enter resolves zone input by ID, 1-based list index, or display name
(case/whitespace-insensitive, "the " prefix tolerated), and rejects
unknown / tier-locked zones plus duplicate concurrent runs without
clobbering the active run. Status reports current room / cleared /
loot / GM mood with a band label (effusive/friendly/neutral/grumpy/
hostile per design doc §3.2). Map prints an ASCII layout with ✓ for
cleared rooms and ▶ for the current room. Advance is a deliberate D1c
stub that calls markRoomCleared and reports the next room — combat
resolution per room wires in D1e. Abandon delegates to abandonZoneRun
and is idempotent (no-active surfaces a friendly DM, not an error).
Wired into adventure.go OnMessage right after the existing D&D
commands. 10 new tests cover dispatch (no-char nudge, list with char,
enter by ID and by index, unknown-zone rejection, duplicate-run guard,
status/map with no run, advance moves the room cursor, abandon clears
active and is idempotent), resolveZoneInput across ID/display/index/
case forms, and gmMoodLabel band edges. Full repo test suite green
(modulo a pre-existing RNG flake in
TestSimulateCombat_FirstAttackBonusImprovesEarlyHits unrelated to this
change).
TwinBee narration + mood triggers land in D1d.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dnd_zone_run table (run_id, zone_id, room_seq_json, rooms_cleared,
boss_defeated, abandoned, loot_collected, gm_mood, started_at,
last_action_at, completed_at) added to db.go schema with active-run
index.
dnd_zone_run.go ships RoomType (entry|exploration|trap|elite|boss),
DungeonRun struct, generateRoomSequence (Entry → ExplorationxN1 → Trap
→ ExplorationxN2 → Elite → Boss within zone's [MinRooms, MaxRooms]),
and persistence helpers: startZoneRun (gates on level tier + active-run
exclusivity), getActiveZoneRun, getZoneRun, markRoomCleared
(advances and auto-completes on boss kill), abandonZoneRun,
adjustGMMood (clamped to [0,100]), addLoot.
8 new tests covering room-sequence shape (entry first, boss last,
exactly one trap/elite, ≥2 explorations), happy-path start,
concurrent-run rejection, unknown-zone rejection, full advance-to-boss
flow with completion, abandon idempotency, GM mood clamping at both
bounds, and loot accumulation order. Full repo test suite green.
Boss-room behavior, !zone command surface, and TwinBee narration
arrive in D1c/D1d.
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>
Hunter / Beast Master / Gloom Stalker L10/L15 capstones — last subclass
batch of Phase 10 SUB3.
Hunter L10 Multiattack (Volley/Whirlwind): +15% DamageBonus, collapsing
the AoE extra-target throughput onto the single 1v1 target.
Hunter L15 Superior Hunter's Defense: ~15% incoming damage reduction
(Uncanny Dodge proxy — most generally applicable of the three picks).
Beast Master L10 Share Spells: pet floor lifts to PetAttackProc 0.45 /
PetAttackDmg 6 + lvl/2, plus +5% DamageBonus rider for the Ranger's own
buffed swings. L15 Superior Bond: pet floor 0.55 proc / 8 + lvl/2 dmg
plus 0.92 DamageReduct ("body-blocks more reliably"). Both still use
the existing max-style floors so a stronger external pet (legacy
adventure pet, etc.) is preserved.
Gloom Stalker L10 Stalker's Flurry: +12% DamageBonus representing
recovered misses across the fight (L7 already covers the first-attack
re-roll via AssassinateAdvantage). L15 Shadowy Dodge: ~10% incoming
damage reduction (reaction-imposed disadvantage proxy).
11 new tests; ranger suite + full plugin suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Life/War/Trickery Domain L10/L15 capstones.
L10 Divine Strike (all 3 subclasses): +4 flat per weapon hit, scaling
to +9 at L14. New CombatModifiers.DivineStrikePerHit channel, gated on
Weapon != nil since 5e specs "weapon hit". Damage type (radiant/weapon/
poison) varies by domain but isn't tracked by the engine.
Life L15 Supreme Healing: heal-spell dice resolve at max instead of
rolled. Wired through resolveHealOutOfCombat via lifeDomainSupremeHealing
helper.
War L15 Avatar of Battle: 5e physical resistance vs. non-magical
weapons → flat 0.80 DamageReduct (softer than full 50% to account for
elemental hits).
Trickery L15 Improved Duplicity: 5e duplicates grant ally-flank
advantage; no allies in 1v1, so proxied passively as +1 SporeCloud
round and +5% damage (duplicates flicker around the foe).
10 new tests; cleric-suite green. Pre-existing rng flakes
(TestOrcRageFiresOnLowHP, TestSimulateCombat_FirstAttackBonus...) are
unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Evocation L10 Overchannel maximizes leveled-spell dice (slot 1-5),
proxied as +50% SpellPreDamage on the queued cast. Self-damage drawback
omitted — our model queues at most one cast/fight, so the "first per
rest" exemption applies. L15 Sculptural Mastery skipped: AoE-only, no
allies in 1v1 (same shape as SUB2b skips).
Abjuration L10 Spell Resistance → DamageReduct *= 0.92 (8% generic DR
proxy; the saves-advantage half is inert since enemies rarely force
saves). L15 Spell Reflection → ReflectNext += 0.30, riding the existing
reflect channel since Counterspell is reaction-deferred to Phase 11.
Necromancy L10 Command Undead has no surface in 1v1 (no thrall/ally
system), re-fluffed as deeper authority over death amplifying the
harvest itself: GrimHarvest multipliers tick up by 1 (2x→3x non-necrotic,
3x→4x necrotic). L15 Improved Undead Thralls proxied as a permanent
skeletal minion via PetAttack channel (proc 0.30, dmg 6 + CON mod);
respects existing higher-pet dmg/proc.
11 new tests; full plugin suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L10 Use Magic Device: no scroll/wand item system to gate, so we proxy the
"now allowed to use magic items" fantasy as a small per-fight wand zap
(FlatDmgStart += 6) plus a +5% DamageBonus representing better tactical use
of magical implements.
L15 Thief's Reflexes: engine has no "extra turn round 1" primitive, so we
ride the AssassinateBonusDmg + AssassinateAdvantage channel (same one Gloom
Stalker's Dread Ambusher uses for bonus opener damage). Flat +8 on first
hit approximates one extra turn's hit; advantage on first attack covers
the "you swing first" piece.
3 new tests cover the L10 thresholds, L9 gate, and the L15 layer stacking
on top of L10. Full suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L10 Improved Combat Superiority — superiority dice grow d8 → d10. The
two scalar maneuvers (Precision Attack +4→+5; Rally heal +4→+5) bump in
their Apply hooks; Trip Attack has no scalar to scale.
L15 Relentless — 5e regenerates one die when initiative is rolled with
an empty pool. Proxied as +1 max pool size (4→5), which gives the same
"always at least one die per encounter" guarantee across the long-rest
cycle without introducing per-fight regen state.
subclassResourceMax now takes level; initSubclassResources upserts the
max (and bumps current by the delta) so existing characters grow on
level-up. Hooked into the dnd_xp level-up loop so Battle Masters
crossing L15 see the new die without waiting for a long rest. 4 new
tests; no regression in subclass/resource/level-up paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Champion L10 Additional Fighting Style → +1 AC + 10% damage (Defense +
Dueling collapse, since one-shot combat can't surface a style picker).
Champion L15 Superior Critical → CritThreshold drops 19 → 18.
Berserker L10 Intimidating Presence → 2 rounds of SporeCloud miss
chance (Action-based fright proxied via the existing fog-cloud channel).
Berserker L15 Retaliation → +15% damage (reaction-strike average uplift,
since the engine has no reaction primitive).
6 new tests; full plugin suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L5/L7 abilities for the three Ranger subclasses, plus Gloom Stalker
Stealth advantage from Umbral Sight. Beast Master pet floors use a
max-style merge so the legacy adventure-pet path can still upgrade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Channel Divinity resource pool (2/long-rest) shared across all three
Cleric domains. One armed expression per fight, mirroring Battle Master.
Life Domain
- L5 Disciple of Life: heal spells restore +(2 + slot level) extra HP
(lifeDomainHealBonus, hooked into resolveHealOutOfCombat).
- L5 Preserve Life: Channel Divinity heal — sets HealItem to 5×Cleric
level, capped at HPMax/2; fires when player drops below 50%.
- L7 Blessed Healer skipped — no allies in 1v1 combat to "heal an ally".
War Domain
- L5 War Priest passive: +1 attack bonus + 0.15 DamageBonus, proxy for
bonus-action extra-attack throughput one-shot combat can't model
discretely.
- L5 Guided Strike: Channel Divinity → +10 to first attack roll
(FirstAttackBonus).
- L7 War God's Blessing skipped — ally reaction, no allies.
Trickery Domain
- L5 Invoke Duplicity: Channel Divinity → AssassinateAdvantage (reroll
first miss) + 0.10 DamageBonus, proxy for "advantage on attacks vs.
creatures adjacent to the duplicate".
- L7 Cloak of Shadows passive: +2 SporeCloud rounds (15% enemy miss)
proxy for the brief invisibility window.
- L5 Blessing of the Trickster skipped — non-combat Stealth flavor.
15 new tests, all green. Same pre-existing
TestSimulateCombat_FirstAttackBonusImprovesEarlyHits flake.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Evocation L7 Empowered Evocation adds INT mod (min +1) to Mage evocation
spell damage. Abjuration L5 Arcane Ward grants a 2×level HP buffer
absorbed before player HP, +prof at L7. Necromancy L5 Grim Harvest heals
2× spell level on spell-kill (3× necrotic) via post-combat hook.
Sculpt Spells / Potent Cantrip / Inured to Undeath skipped — no allies,
save-half is already uniform, and no necrotic enemy damage to resist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slots into the Phase 9 spell pipeline so an L5+ Rogue who picks Arcane
Trickster becomes a third-caster on the Mage list, INT-based.
- isSpellcaster(c) gate: full casters by class plus AT Rogue at L5+.
!cast / !spells now consult this; the cast class-gate also accepts
Mage-tagged spells when the player is an AT Rogue.
- subclassSpellSlots() / slotsForCharacter() / setSpellSlotsForCharacter()
layer subclass slots on top of the class baseline. AT pool: 3 L1 at
L5–6, 4 L1 + 2 L2 at L7+, +2 L3 at L13+ (third-caster table).
- spellcastingMod returns INT for AT Rogue.
- L7 Magical Ambush proxy: spellSaveDC bumps +2 once Rogue+AT hits L7,
standing in for "targets have disadvantage when casting from hidden"
since the one-shot combat layer doesn't model hidden state.
- ensureSpellsForCharacter bootstraps a Mage-list starter spellbook
(minor_illusion, shocking_grasp, magic_missile, shield, sleep at L5;
mirror_image + misty_step at L7+; hypnotic_pattern at L13+) and
refreshes the slot pool on level/subclass change.
- applySubclassChoice runs ensureSpellsForCharacter for AT so the
spellbook is provisioned at selection time (idempotent).
- !cast clamps upcasts to highestAvailableSlotForChar when AT, since
third-caster's slot ceiling lags behind the spell list.
Tests added: AT spellcaster gate (L5 cutoff, non-AT Rogue rejected), INT
mod, subclass slot pool by tier, L7 ambush DC bump, ensureSpells default
list (all on Mage list, slots correct), end-to-end !cast queues
magic_missile for AT Rogue, !cast rejects plain Thief Rogue.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Battle Master:
- New "superiority" resource pool (4/long-rest at L5), provisioned via
initSubclassResources at !subclass selection.
- Three armed maneuvers fueled by superiority dice:
* Precision Attack — +d8 (≈+4) to first attack roll
* Tripping Attack — enemy skips first attack (reuses Phase 9
SpellEnemySkipFirst)
* Rally — +(d8 + CHA) HealItem at <50% HP
- L7 Know Your Enemy proxied as +1 AttackBonus passive.
Assassin:
- L5 Assassinate: AssassinateAdvantage (re-roll first miss, take better
of two d20s) + AssassinateBonusDmg = level (5 at L5) stacked on top
of the Rogue's existing Sneak Attack auto-crit.
- L7 Impostor bumps the bonus damage by +3.
- L5 Infiltration Expertise → +5 Deception; L7 Impostor → +10.
Engine:
- CombatModifiers gains FirstAttackBonus, AssassinateAdvantage,
AssassinateBonusDmg.
- resolvePlayerAttack consumes each on the first attack only via new
combatState one-shot flags.
Tests added: 14 covering passive gating (BM L7+, Assassin L5/L7),
maneuver Apply flags, superiority pool init for BM only, !arm gating
across class/subclass for precision_attack, Deception bonus tiers,
statistical lift from FirstAttackBonus and AssassinateBonusDmg in
SimulateCombat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First mechanical slice of the subclass system. Each subclass gets its L5
and L7 abilities wired through the existing combat engine + skill check
substrate; no new resource pools introduced.
Champion (L5/L7):
- Improved Critical: new Mods.CritThreshold lowers crit floor to nat 19+
in resolvePlayerAttack. Default 20 preserved for non-Champions.
- Remarkable Athlete: +½ proficiency bonus to STR/DEX/CON skill checks
via subclassSkillBonus, layered on raceSkillBonus.
Berserker (L5/L7):
- Rage: new !arm-able active ability gated to Berserker subclass via
DnDAbility.Subclass field. Reuses the Fighter stamina pool (3/long-rest
matches 5e's rage uses). On fire: +2 flat damage per hit, halve incoming
weapon damage, +50% damage approximation for Frenzy's bonus-attack-per-
turn (one-shot combat can't model that literally).
- Frenzy exhaustion: new exhaustion column on dnd_character; incremented
by persistDnDPostCombatSubclass after any rage'd combat. Decremented by
one per long rest.
- Mindless Rage: documented; charm/frighten conditions don't exist in the
combat engine yet, so no functional effect until P11 zone bosses land.
Thief (L5/L7):
- Sleight of Hand added to dndSkillTable.
- Fast Hands: +5 to Sleight of Hand checks via subclassSkillBonus.
- Supreme Sneak: advantage on Stealth (best of two d20s) via new
subclassSkillAdvantage hook in performSkillCheck. 5e's half-speed gate
dropped — we don't track movement speed.
Plumbing:
- applySubclassPassives layered after applyClassPassives in both arena
and dungeon combat bridges.
- DnDAbility.Subclass enables per-subclass gating; characterActiveAbilities
filters !arm and !abilities lists by both class and subclass.
- Existing !respec full-wipe also clears Exhaustion.
Tests: Champion CritThreshold gating across L4/L5/no-subclass; rage Apply
flag-set; rage probabilistic win-rate lift vs tougher enemy; exhaustion
increment only on raged combats; long-rest decrement; Champion bonus on
STR/DEX/CON only and only at L7+; Thief Fast Hands SoH-only; Thief
Supreme Sneak L7+ Stealth-only; statistical advantage roll lifts Thief
stealth average by ≥2 vs plain Rogue; !arm rage subclass gating; schema
roundtrip; characterActiveAbilities visibility.
Implements the identity layer of gogobee_subclass_system.md: 15 subclasses
(3 per class) selectable at L5 via !subclass.
- Schema: new subclass + last_subclass_respec_at columns on dnd_character.
- Registry (dnd_subclass.go): 15 entries with class, display, tagline, and
TwinBee flavor line; helpers for prompt rendering and input resolution
(number, id, or display-name; case/space/hyphen-insensitive).
- !subclass command: bare form prints prompt or current; !subclass <name>
selects (free first time) or changes (500 euros + 30-day cooldown via
separate timestamp from the full !respec wipe). Save-then-debit ordering
mirrors !respec audit fix B.
- Level-up DM at L5+ now embeds the real selection prompt instead of the
Phase 9 placeholder; suppressed once a subclass is set.
- Sheet shows the subclass below the class line, or nudges !subclass when
unchosen at L5+.
- Existing !respec full-wipe also clears subclass + cooldown timestamp.
Mechanical effects of L5/7/10/15 subclass abilities deferred to SUB2/SUB3.
- Enforce per-level Mage known-spell cap in handleSpellsLearn; surface
spellbook budget in renderSpellsList.
- Mage level-up DM now nudges with "Spells available to learn: N" via
extracted buildLevelUpMessage.
- Extract halveSavedDamage and enemySpellSaveMod for clarity; document
single-target AoE limitation in applySpellDamageSave.
- Add tests: mage learn cap, prepare flow, AoE behavior, spell save
rounding, spells migration.
applyPendingCast resolves c.PendingCast against the upcoming fight before
SimulateCombat. Damage spells (Fire Bolt, Burning Hands, Magic Missile,
Fireball, etc.) emit a pre-combat spell_cast event via new
CombatModifiers.SpellPreDamage{,Desc}. Control spells (Hold Person, Sleep,
Command) set SpellEnemySkipFirst so the engine skips the enemy's round-1
attack with a spell_held event; Hold-family also primes AutoCritFirst.
Buffs (Mage Armor, Bless, Hunter's Mark, Shield of Faith, Aid, Spiritual
Weapon, Mirror Image, Greater Invisibility) fold into stats/mods directly.
Concentration-on-damage break is left for Phase 11 (turn-based bosses);
ConcentrationSpell persists across fights until manually dropped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Adventure characters now record DeathSource ("adventure"|"arena") and
DeathLocation when killed. Threaded through Kill() and transitionDeath.
Daily report uses the recorded death location instead of misattributing
arena deaths to the day's adventure log: when DeathSource != "adventure",
the adventure block shows the alive icon and a separate "Later fell in
{location}." line. Standout-loss flavor uses DeathLocation. Empty
DeathSource (legacy rows) falls back to current behavior.
- calcDamage applies a ±15% per-hit jitter, so successive hits in a phase
no longer produce identical numbers. Previously every hit in a phase was
flat (e.g. 17, 17, 17, 17) and mirror-symmetric between player/enemy,
reading as scripted.
- assessThreat rewritten to use the engine's actual penetration formula:
damage-per-round each direction, then rounds-to-kill ratio. The old
additive HP+Attack*3 model ignored Defense, so even fights could be
rated trivial — players died after consumables were skipped with the
"threat assessed as manageable" message.
- SelectConsumables never skips on trivial when arenaRound > 0. Arena
losses cost real money and equipment, so the cost of a wrong assessment
is too high to gamble on; adventure-side fights still skip trivial
threats to avoid wasting items on chump enemies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Co-op: when a run wipes or completes, clear the 99-sentinel combat
lock for participants and pin combat_actions_used to maxCombatActions.
Previously the lock only released on the next midnight reset, so
members were stuck unable to combat for the rest of the day after
their run ended.
- Holidays: !holidays now prints the full list. Previously it called
the same featured-only formatter as TwinBee's morning auto-post,
so users saw the same truncated message twice with a misleading
"Use !holidays for the full list" hint that pointed at itself.
- Forex: add currency conversion. Auto-detects when the first arg is
numeric (`!fx 1500 USD EUR`) and also accepts an explicit `convert`
subcommand. Tolerant parsing: "to"/"into"/"in"/"=" filters, comma
and k/m suffix support, and any of `1500 USD EUR`, `1500 USD/EUR`,
`USD/EUR 1500` orderings. Includes parser + comma-formatter tests.
- Lottery: drop 2-match prize tier. At 1/16 odds × €25 it was +EV per
ticket on its own and bled the community pot (~€775/wk recently),
preventing jackpot rollover from accumulating. Removed from scoring,
payout loops, draw announcement, !lottery odds, and !lottery history.
Match2Winners DB column kept (history rows preserved); always written
as 0 going forward.
- Lottery: shift week boundary so Sat/Sun return next Monday. Friday's
23:59 draw used to leave players locked at their 100-ticket cap until
next Monday rolled in; now the cap resets immediately after the draw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both are 16MB Go binaries built from cmd/gensolver and cmd/holdem-train.
They were showing up as untracked on every status check. Also drops a
stray 0-byte internal/db/gogobee.db that someone created at the wrong
path (real DB lives in data/, already gitignored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Status sheet now shows lifetime craft count + a hint pointing at
!adventure recipes (only displayed once the player has crafted at
least once — keeps the line absent for non-foragers).
- README gains a Crafting subsection documenting the auto-craft flow,
XP rewards, level gates, the recipes command, and the consumable
protection (Robbie + sell-all both skip them).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Players had no way to see what they could craft without grinding
ingredients and watching combat narrative. New command lists every recipe
unlocked at the player's current Foraging level, grouped by tier, with:
- Result name + ingredient list (so players know what to gather)
- Per-recipe success rate at the player's current level
- Locked-recipe count + next-unlock threshold
- Max auto-crafts per combat (1 + (level-10)/10)
Sub-Foraging-10 players see only the unlock hint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Crafting was a downstream consumer of Foraging skill but never gave any
XP back, so the feedback loop was one-directional — only gathering
contributed to the level that gates better recipes.
Now: each auto-craft attempt grants Foraging XP (success > failure).
Per-tier values:
tier 1: +12 / +3
tier 2: +25 / +5
tier 3: +40 / +8
tier 4: +60 / +12
tier 5: +90 / +18
Successful T1 ≈ 30% of a Foraging Success haul; T5 ≈ 40%. Failures get a
small consolation grant — the attempt still produced practical knowledge
(and lost ingredients).
Schema: adventure_characters.crafts_succeeded INT for lifetime tracking.
Migration entry included.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consumables (auto-crafted from foraged ingredients or dropped from T2+
activities) are a curated stockpile — selling them should be an explicit
choice, not a side effect.
Two surfaces tightened:
- Robbie's qualifying-items filter now skips Type == "consumable" the
same way it skips Arena gear and cards.
- !adventure sell all leaves consumables in inventory and reports them
in the kept-items tally. The merchant message points players at
!adventure sell <name> for explicit consumable sales.
!adventure sell <name> still works fine on consumables — that path is
already explicit by definition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes that were running in prod but hadn't been committed:
1. Sender-private DM routing. When a player invokes a holdem command from
their DM, the dispatcher rewrites ctx.RoomID to the game room so
actions affect the right table. But sender-private replies (errors,
help, status, "unknown command") would then post to the public games
room instead of back to the DM that asked. Now: the dispatcher saves
ctx.OriginRoomID = the DM, and a new reply() helper routes sender-
private messages to OriginRoomID when set, falling back to in-thread
reply otherwise. (OriginRoomID was added to MessageContext earlier.)
2. broadcastDM jitter. 150-400ms sleep between sends to avoid Matrix
rate-limit bursts when a game has 4+ players each in their own DM.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Random event gold ranges (€20-250) were authored against an earlier
economy and have been near-trivial against current per-action haul values
(thousands at higher tiers). Scaled the GoldMin/GoldMax across all event
entries by 100× so the rewards feel like real moments instead of
rounding errors.
Pure flavor-table numeric rebalance — no logic changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous scheduler built one fixed roll-minute per player per UTC day in
the 10:00-16:00 window, regardless of when (or whether) the player
actually played. Players who acted late in the day might miss their roll
entirely; players who acted early still had to wait for an arbitrary
mid-day window.
New scheduler:
- Each ticker minute, scan for players who have acted today but don't
yet have a scheduled roll. Assign each a one-shot roll-minute 60-180
minutes in the future (capped to 23:50 UTC).
- At the assigned minute, fire the 0.5% trigger roll. Mark advEventRolled
so it won't fire again that day.
- New day rebuilds both maps fresh.
Result: events trigger relative to the player's actual activity, not
clock time. Late-day actors get their chance; players who skip a day
don't get a roll.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NPC encounter pending interactions were using advDMResponseWindow
(the short DM-reply window), so a player who didn't get to their DMs
quickly would lose the encounter prompt. Bumped to "until midnight UTC"
so the encounter is available throughout the day the NPC arrived.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, advOverlevelMultiplier penalized every action where the
player's effective level exceeded the location's MinLevel by 4+, with
no escape valve. That punished players grinding at their max-available
tier (e.g., a level 50 character at the highest mining location they
qualify for, with nothing higher unlocked).
Now: the multiplier short-circuits to 1.0 when no accessible higher-tier
location of the same activity exists for that player. Signature changed
from (effectiveLevel, minLevel) to (effectiveLevel, *AdvLocation) so the
helper can scan the activity's location list. combat_bridge.go updated
to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multiple baskets (or multiple mimics) sent during the same day now share
one game-room post, one vote, one resolution. First-in becomes the stack
"lead"; subsequent same-type sends become followers that inherit the
lead's deadline and votes.
Behavior:
- Send a basket → new lead, new post, 6h timer starts
- Send another basket within the window → silently joins the stack,
edits lead's post to bump count
- At stack size 2, TwinBee adds a "this gift looks REALLY special" line
(one of 5 variants picked deterministically by lead id). No further
escalation as the stack grows
- One !coop giftvote on the lead's id covers the whole stack
- Resolution applies the same outcome to every gift in the stack;
modifier is N × ±6
- End-of-run gift log groups by stack with all senders attributed
Schema: coop_dungeon_gifts.stack_lead_id INTEGER (NULL for lead/standalone,
otherwise points at lead's id). Migration entry included.
Why first-in deadline (vs extending on each follower): exploitability.
Saboteurs could spam-extend a stack to delay resolution. Locked deadline
keeps senders honest about timing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Players in a co-op were getting their combat action back every midnight
reset and offered the dungeon option in the morning DM. Per spec they
should be "off in the Co-op" and unable to also solo-grind dungeons.
Fix at the DB layer (so all CanDoCombat() paths agree):
- New helper lockCoopCombatActions() sets combat_actions_used=99 for any
user in an active coop run. Called immediately after every
resetAllAdvDailyActions() — at startup and at midnight.
- Render layer: morning DM now shows "combat locked (in Co-op #N, day
X/Y)" and replaces the dungeon section with an explanatory line
pointing them at !coop status.
Combat returns to normal automatically once the run completes/wipes —
the next midnight reset finds status != 'active' and skips the lock.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each gift now has its own 6h voting window, but the only places the
remaining time was visible were the original arrival post and any
edit-triggered re-render after a vote. Mid-window the displayed
countdown was stale.
!coop status now lists pending gifts (anything with vote_result still
NULL on this run) with current open/leave tally and a fresh per-gift
countdown derived from expires_at. The party can poll this any time to
see what's about to close.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The TwinBeeGiftArrival pool entries each end with a templated footer
authored to the spec format ("Open: {count} · Leave it: {count}\nMajority
rules. Ties go to {leader}."), but the placeholders were never being
substituted — players saw the literal "{count}" and "{leader}" strings.
Substitute in renderCoopGiftPost: ordered Replace for the two {count}
positions (opens, then leaves), ReplaceAll for {leader}. Also dropped
the redundant "Current votes:" line my code was appending — the flavor's
own tally line covers it after substitution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the single daily-tick gift resolution with independent per-gift
expiries. Each gift now has its own 6h voting window; once that elapses,
the votes are tallied, the sender gets DM feedback, and the live game-room
post is edited to reveal the type and resolved modifier. The modifier
sits on the run waiting for the next floor resolution to merge it in.
Effect:
- Gifts fire continuously throughout the day rather than all at once at
08:00 UTC, surfacing sender activity in real time.
- Senders get fast feedback (~6h instead of waiting for the next daily
tick).
- Floor resolution becomes purely "sum pending modifiers from already-
resolved gifts" — cleaner separation of concerns.
- Atomic per-gift apply via markCoopGiftApplied (UPDATE...WHERE applied_at
IS NULL) prevents double-application on retry.
Schema additions:
- coop_dungeon_gifts.expires_at (when voting closes)
- coop_dungeon_gifts.applied_at (when modifier merged into a floor)
- Migration entries provided.
Defensive backstop: floor resolution still force-resolves any gifts whose
expiry was missed (e.g., bot down during the timer window).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous guard skipped resolution if lock_date == today_date, pushing
first-day windows to 24-32h regardless of when in the UTC day the lock
fired. With per-minute lock checks, the original same-tick race no longer
exists, so the date guard was just adding latency.
New guard: at least 12h since lock. Late-night locks still get >12h
funding/voting windows; early-morning locks get 24h+ as before. No effect
on the existing run #1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>