From 3fe2d2f0ca1a8a34bd84536c9849891f2d9aa103 Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Sat, 9 May 2026 07:51:10 -0700 Subject: [PATCH] Adv 2.0 Phase L2 step 3: arena boss-shaped bestiary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reshapes the legacy ArenaMonster data into a boss-shaped arenaBosses map of DnDMonsterTemplate entries — the carrier the upcoming resolveArenaBoss (step 4) will hand to runZoneCombat + renderBossOutcome. IDs namespaced "arena_t_r" so they don't collide with dndBestiary. HP/AC/Attack scale with tier via arenaTierBaseStats; within a tier, BaseLethality biases each monster by a ±30% band so round-1 anchors the bottom of the tier and round-4 the top. Stats are first-pass — final tuning happens during the ARENA_BOSS_FLOW flag soak. arenaBossPhaseTwoAt(tier) gates phase-two narration: T1–T2 = none, T3+ = 50% HP. The T4–T5 flavor barb piggybacks on the existing bossPhaseTwoPool path through ZoneArena. No callers yet — wiring lands in step 4. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/plugin/adventure_arena_monsters.go | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/internal/plugin/adventure_arena_monsters.go b/internal/plugin/adventure_arena_monsters.go index 0229fb4..36c2f8d 100644 --- a/internal/plugin/adventure_arena_monsters.go +++ b/internal/plugin/adventure_arena_monsters.go @@ -1,5 +1,7 @@ package plugin +import "fmt" + // ── Arena Tier & Monster Definitions ──────────────────────────────────────── // // Five tiers, four monsters each. Death chance, rewards, and XP scale with tier. @@ -190,6 +192,93 @@ func arenaGetMonster(tier, round int) *ArenaMonster { return &t.Monsters[round-1] } +// ── Adv 2.0 boss-flow bestiary (Phase L2 step 3) ──────────────────────────── +// +// arenaBosses re-shapes the legacy arenaTiers data into boss-shaped +// DnDMonsterTemplate entries so resolveArenaBoss (Phase L2 step 4) can +// run an arena fight through the same runZoneCombat → renderBossOutcome +// flow that zone bosses use. Keyed by arenaBossID(tier, round) — IDs +// are namespaced "arena_t_r" so they don't collide with +// dndBestiary keys. +// +// HP/AC/Attack are first-pass tier-banded values; BaseLethality biases +// each monster up or down within its tier band so round-1 is the +// weakest fight and round-4 is the cap. Final tuning happens during +// the ARENA_BOSS_FLOW flag soak (gogobee_legacy_migration.md §4 Risk). +var arenaBosses = map[string]DnDMonsterTemplate{} + +// arenaBossID composes the canonical arena bestiary key. +func arenaBossID(tier, round int) string { + return fmt.Sprintf("arena_t%d_r%d", tier, round) +} + +// arenaBossPhaseTwoAt returns the PhaseTwoAt fraction (of MaxHP) for an +// arena fight at the given tier. T1–T2 = no phase two; T3+ = 50% HP. +// The T4–T5 flavor barb piggybacks on the standard phase-two narration +// path through ZoneArena's bossPhaseTwoPool (populated alongside arena +// flavor when those lines land). +func arenaBossPhaseTwoAt(tier int) float64 { + if tier >= 3 { + return 0.5 + } + return 0 +} + +// arenaTierBaseStats returns the (HP, AC, Attack, AttackBonus, CR) +// baseline for a given arena tier. arenaMonsterToTemplate then biases +// HP/Attack by BaseLethality so within-tier ordering matches the legacy +// difficulty curve. +func arenaTierBaseStats(tier int) (hp, ac, atk, ab int, cr float32) { + switch tier { + case 1: + return 30, 13, 8, 4, 1 + case 2: + return 80, 14, 14, 6, 4 + case 3: + return 160, 15, 22, 7, 8 + case 4: + return 260, 16, 32, 8, 13 + case 5: + return 400, 18, 45, 10, 18 + } + return 30, 13, 8, 4, 1 +} + +// arenaMonsterToTemplate maps a legacy ArenaMonster onto a +// DnDMonsterTemplate. HP/AC/Attack scale with tier; within a tier, +// BaseLethality biases the curve via a ±30% band so round-1 sits at +// the bottom of the tier and round-4 at the top. +func arenaMonsterToTemplate(id string, m ArenaMonster, tier int) DnDMonsterTemplate { + hp, ac, atk, ab, cr := arenaTierBaseStats(tier) + bias := (m.BaseLethality - 0.5) * 0.6 // ≈ -0.24 .. +0.29 + hp = int(float64(hp) * (1 + bias)) + atk = int(float64(atk) * (1 + bias)) + if hp < 1 { + hp = 1 + } + if atk < 1 { + atk = 1 + } + return DnDMonsterTemplate{ + ID: id, Name: m.Name, CR: cr, + HP: hp, AC: ac, Attack: atk, AttackBonus: ab, Speed: 12, + BlockRate: 0.05, + Ability: m.Ability, + XPValue: arenaTiers[tier-1].BattleXP * 4, + Notes: m.Flavor, + } +} + +func init() { + for ti, t := range arenaTiers { + tier := ti + 1 + for ri, m := range t.Monsters { + id := arenaBossID(tier, ri+1) + arenaBosses[id] = arenaMonsterToTemplate(id, m, tier) + } + } +} + // ── Arena Death Flavor Text ───────────────────────────────────────────────── var arenaDeathMessages = []string{