N5 review fixes: protect keys from Robbie, fire epilogue on autopilot, atomic finale reward

Five correctness fixes from a code review of the N5 branch:

- Robbie no longer sweeps/sells cross-zone keys (Type "key"), which
  permanently broke the vault unlock they exist to open.
- Robbie's gift tier now reads the canonical DnD level, not the frozen
  legacy CombatLevel that pegged every gift at tier 1.
- Boss epilogue (D1b) now fires on the compact autopilot boss resolve —
  the primary long-expedition path — not just manual !fight. Deduped the
  two manual sites into a shared writeBossEpilogue helper.
- Finale reward latches epilogue_cleared before granting the Legendary +
  title, so a failed/late write can't make the reward repeatable.
- Misty arc beat's occupied-slot guard moved above the counter increment,
  so a contended pending slot defers the encounter instead of consuming a
  5/15/30 beat forever.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
prosolis
2026-07-10 16:53:32 -07:00
parent 3103b519fb
commit 81b2359109
6 changed files with 48 additions and 19 deletions

View File

@@ -171,7 +171,10 @@ func (p *AdventurePlugin) robbieVisitPlayer(userID id.UserID, displayName string
if err == nil {
char.RobbieVisitCount++
if char.RobbieVisitCount%robbieGiftEveryNVisits == 0 {
if gifts := consumableCache(robbieGiftTier(char.CombatLevel), 1); len(gifts) > 0 {
// Use the canonical DnD level (like the arena's tier gate), not the
// frozen legacy CombatLevel — that snapshots at 13 once D&D setup
// completes, so reading it here would peg every gift at tier 1.
if gifts := consumableCache(robbieGiftTier(arenaDnDLevelOrZero(userID)), 1); len(gifts) > 0 {
if err := addAdvInventoryItem(userID, gifts[0]); err == nil {
leftGift = &gifts[0]
}
@@ -200,10 +203,12 @@ func (p *AdventurePlugin) robbieVisitPlayer(userID id.UserID, displayName string
func robbieQualifyingItems(inv []AdvItem, equip map[EquipmentSlot]*AdvEquipment) []AdvItem {
var result []AdvItem
for _, item := range inv {
// Never touch Arena gear, cards, or consumables. Consumables are a
// player-curated stockpile (crafted or dropped); selling them is an
// explicit decision the player must make themselves.
if item.Type == "ArenaGear" || item.Type == "card" || item.Type == "consumable" {
// Never touch Arena gear, cards, consumables, or keys. Consumables are
// a player-curated stockpile (crafted or dropped); selling them is an
// explicit decision the player must make themselves. Keys are cross-zone
// unlock tokens (N5/D4) that must persist in inventory to open their
// vault later — sweeping one permanently breaks that unlock.
if item.Type == "ArenaGear" || item.Type == "card" || item.Type == "consumable" || item.Type == "key" {
continue
}