mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
In-place node kind swaps on fork branches — no node added/removed, longest path unchanged — adding one Elite + one Trap of anchor variety to each T4/T5 zone, placed to keep fork-choice risk/loot asymmetric: underdark +Elite drow_gate (R2->R4 region-guardian) +Trap silenced_chamber feywild +Elite singing_orchard (grove branch) +Trap mire_steps (marsh) dragons_lair+Elite coin_strewn_hall (treasure spur) +Trap hidden_passage (hoard spur) abyss_portal+Elite wardens_hall (R3 guardian) +Trap hush_corridor, seam_threshold abyss_portal shipped with zero trap nodes; D10 gives it two. A/B sim corpus (n=480/side): boss-clear flat (+1.5pp aggregate, per-cell within n=15 noise), median day-counts stable, combats/run rose where anchors hit common paths. New Test*Graph_*Anchors tests lock the per-zone trap/elite counts.
274 lines
15 KiB
Go
274 lines
15 KiB
Go
package plugin
|
||
|
||
// Dragon's Lair (Infernus Peak) branching graph — multi-region.
|
||
//
|
||
// Long-expedition plan D1-e: extended from the original 12-node sketch
|
||
// to the new T5 length band (36–44 rooms traversed). Topology preserves
|
||
// the G8g design intent — long approach, mid-zone binary-converging
|
||
// fork, capstone 3-way fork — and adds the linear depth each region
|
||
// now needs to feel like its own sub-dungeon. D1-e also backfills the
|
||
// missing RegionID authoring per dnd_expedition_region.go: every node
|
||
// carries a valid RegionID matching the registry.
|
||
//
|
||
// The original "fork2 capstone leaves go straight to boss" shape is
|
||
// adjusted so the three capstones now converge at an infernax_doors
|
||
// MERGE in R4, then walk a short throne approach to boss. Three
|
||
// distinct paths to boss are preserved (each via its own capstone
|
||
// node); the merge just consolidates the long final hall instead of
|
||
// triplicating it.
|
||
//
|
||
// R1 kobold_warrens preamble (10 nodes):
|
||
// entry → kobold_lookout → kobold_warrens → smoke_choked_hall →
|
||
// ash_chapel → kobold_warchief_camp → ember_corridor →
|
||
// obsidian_steps → cinder_passage → kobold_descent
|
||
//
|
||
// R2 drake_pens region (13 nodes; walk length 10):
|
||
// drake_pens → drake_grooming_pit → drake_holding_run →
|
||
// drake_yards → drake_handlers_hall → drake_armory → fork1
|
||
//
|
||
// R2 fork1 — binary, converges at wyrmlings_nest (R3 boundary):
|
||
// ash_bridge spur (unlocked, TRAP):
|
||
// ash_bridge (TRAP) → burning_span → cinder_walk
|
||
// treasure_vault spur (Perception DC 15, denser loot):
|
||
// treasure_vault → coin_strewn_hall → vault_passage
|
||
//
|
||
// R3 the_vault region (9 nodes; walk length 9):
|
||
// wyrmlings_nest (ELITE) → hoard_outer → coin_river → hoard_arch →
|
||
// flame_corridor → infernax_threshold → molten_steps → inferno_walk
|
||
// → fork2
|
||
//
|
||
// R4 infernax_chamber — capstone 3-way fork:
|
||
// direct_confrontation spur (unlocked):
|
||
// charge_walk → direct_confrontation → broken_chamber
|
||
// dragon_bargain spur (CHA DC 16):
|
||
// speakers_step → dragon_bargain → audience_hall
|
||
// hoard_pillar spur (Perception DC 17, SECRET LootBias 2.5):
|
||
// hidden_passage → hoard_pillar (SECRET) → secret_arch
|
||
//
|
||
// R4 final approach (MERGE + boss; walk 6 after capstone spur):
|
||
// infernax_doors (MERGE) → throne_ascension →
|
||
// molten_throne_approach → crown_steps → final_step → boss
|
||
//
|
||
// Longest entry→boss walk: 10 (R1) + 10 (R2) + 9 (R3) + 10 (R4) = 39
|
||
// nodes, inside [36, 44]. The two R2 spurs and three R4 capstones each
|
||
// reach the boss in the same node count by construction.
|
||
//
|
||
// D10 anchor variety (in-place kind swaps, no length change): the two
|
||
// loot-leaning branches each gain a guard, so "the richer route costs
|
||
// more" —
|
||
// - Coin-Strewn Hall (treasure_vault spur) becomes an ELITE; the
|
||
// Perception loot route is now guarded, mirroring the ash_bridge
|
||
// spur's TRAP so both fork1 branches carry an anchor.
|
||
// - Hidden Passage (hoard_pillar SECRET capstone spur) becomes a
|
||
// TRAP guarding the densest loot in the zone.
|
||
// The direct-confrontation and dragon-bargain routes stay clean, so the
|
||
// fork choice trades safety for loot.
|
||
|
||
func zoneDragonsLairGraph() ZoneGraph {
|
||
r1 := "dragons_lair_kobold_warrens"
|
||
r2 := "dragons_lair_drake_pens"
|
||
r3 := "dragons_lair_the_vault"
|
||
r4 := "dragons_lair_infernax_chamber"
|
||
|
||
nodes := []ZoneNode{
|
||
// R1 kobold_warrens preamble.
|
||
{NodeID: "dragons_lair.entry", Kind: NodeKindEntry, IsEntry: true, RegionID: r1,
|
||
Label: "Mountain Pass", PosX: 0, PosY: 1},
|
||
{NodeID: "dragons_lair.kobold_lookout", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Kobold Lookout", PosX: 1, PosY: 1},
|
||
{NodeID: "dragons_lair.kobold_warrens", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Kobold Warrens", PosX: 2, PosY: 1},
|
||
{NodeID: "dragons_lair.smoke_choked_hall", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Smoke-Choked Hall", PosX: 3, PosY: 1},
|
||
{NodeID: "dragons_lair.ash_chapel", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Ash Chapel", PosX: 4, PosY: 1},
|
||
{NodeID: "dragons_lair.kobold_warchief_camp", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Warchief's Camp", PosX: 5, PosY: 1},
|
||
{NodeID: "dragons_lair.ember_corridor", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Ember Corridor", PosX: 6, PosY: 1},
|
||
{NodeID: "dragons_lair.obsidian_steps", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Obsidian Steps", PosX: 7, PosY: 1},
|
||
{NodeID: "dragons_lair.cinder_passage", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Cinder Passage", PosX: 8, PosY: 1},
|
||
{NodeID: "dragons_lair.kobold_descent", Kind: NodeKindExploration, RegionID: r1,
|
||
Label: "Kobold Descent", PosX: 9, PosY: 1},
|
||
|
||
// R2 drake_pens region.
|
||
{NodeID: "dragons_lair.drake_pens", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Drake Pens", PosX: 10, PosY: 1},
|
||
{NodeID: "dragons_lair.drake_grooming_pit", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Grooming Pit", PosX: 11, PosY: 1},
|
||
{NodeID: "dragons_lair.drake_holding_run", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Holding Run", PosX: 12, PosY: 1},
|
||
{NodeID: "dragons_lair.drake_yards", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Drake Yards", PosX: 13, PosY: 1},
|
||
{NodeID: "dragons_lair.drake_handlers_hall", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Handler's Hall", PosX: 14, PosY: 1},
|
||
{NodeID: "dragons_lair.drake_armory", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Drake Armory", PosX: 15, PosY: 1},
|
||
{NodeID: "dragons_lair.fork1", Kind: NodeKindFork, RegionID: r2,
|
||
Label: "The Cinder Crossing", PosX: 16, PosY: 1},
|
||
|
||
// R2 ash_bridge spur (TRAP, unlocked).
|
||
{NodeID: "dragons_lair.ash_bridge", Kind: NodeKindTrap, RegionID: r2,
|
||
Label: "Ash Bridge", PosX: 17, PosY: 0},
|
||
{NodeID: "dragons_lair.burning_span", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Burning Span", PosX: 18, PosY: 0},
|
||
{NodeID: "dragons_lair.cinder_walk", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Cinder Walk", PosX: 19, PosY: 0},
|
||
|
||
// R2 treasure_vault spur (Perception 15, loot).
|
||
{NodeID: "dragons_lair.treasure_vault", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Treasure Vault", PosX: 17, PosY: 2,
|
||
Content: ZoneNodeContent{LootBias: 1.5}},
|
||
{NodeID: "dragons_lair.coin_strewn_hall", Kind: NodeKindElite, RegionID: r2,
|
||
Label: "Coin-Strewn Hall", PosX: 18, PosY: 2},
|
||
{NodeID: "dragons_lair.vault_passage", Kind: NodeKindExploration, RegionID: r2,
|
||
Label: "Vault Passage", PosX: 19, PosY: 2},
|
||
|
||
// R3 the_vault region.
|
||
{NodeID: "dragons_lair.wyrmlings_nest", Kind: NodeKindElite, RegionID: r3,
|
||
Label: "Wyrmling's Nest", PosX: 20, PosY: 1},
|
||
{NodeID: "dragons_lair.hoard_outer", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Outer Hoard", PosX: 21, PosY: 1},
|
||
{NodeID: "dragons_lair.coin_river", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Coin River", PosX: 22, PosY: 1},
|
||
{NodeID: "dragons_lair.hoard_arch", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Hoard Arch", PosX: 23, PosY: 1},
|
||
{NodeID: "dragons_lair.flame_corridor", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Flame Corridor", PosX: 24, PosY: 1},
|
||
{NodeID: "dragons_lair.infernax_threshold", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Infernax's Threshold", PosX: 25, PosY: 1},
|
||
{NodeID: "dragons_lair.molten_steps", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Molten Steps", PosX: 26, PosY: 1},
|
||
{NodeID: "dragons_lair.inferno_walk", Kind: NodeKindExploration, RegionID: r3,
|
||
Label: "Inferno Walk", PosX: 27, PosY: 1},
|
||
{NodeID: "dragons_lair.fork2", Kind: NodeKindFork, RegionID: r3,
|
||
Label: "Hoard Approach", PosX: 28, PosY: 1},
|
||
|
||
// R4 direct_confrontation spur.
|
||
{NodeID: "dragons_lair.charge_walk", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Charge Walk", PosX: 29, PosY: 0},
|
||
{NodeID: "dragons_lair.direct_confrontation", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Direct Approach", PosX: 30, PosY: 0},
|
||
{NodeID: "dragons_lair.broken_chamber", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Broken Chamber", PosX: 31, PosY: 0},
|
||
|
||
// R4 dragon_bargain spur (CHA 16).
|
||
{NodeID: "dragons_lair.speakers_step", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Speaker's Step", PosX: 29, PosY: 1},
|
||
{NodeID: "dragons_lair.dragon_bargain", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Words With Infernax", PosX: 30, PosY: 1},
|
||
{NodeID: "dragons_lair.audience_hall", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Audience Hall", PosX: 31, PosY: 1},
|
||
|
||
// R4 hoard_pillar spur (Perception 17, SECRET).
|
||
{NodeID: "dragons_lair.hidden_passage", Kind: NodeKindTrap, RegionID: r4,
|
||
Label: "Hidden Passage", PosX: 29, PosY: 2},
|
||
{NodeID: "dragons_lair.hoard_pillar", Kind: NodeKindSecret, RegionID: r4,
|
||
Label: "Hidden Hoard Pillar", PosX: 30, PosY: 2,
|
||
Content: ZoneNodeContent{LootBias: 2.5}},
|
||
{NodeID: "dragons_lair.secret_arch", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Secret Arch", PosX: 31, PosY: 2},
|
||
|
||
// R4 final approach.
|
||
{NodeID: "dragons_lair.infernax_doors", Kind: NodeKindMerge, RegionID: r4,
|
||
Label: "The Infernax Doors", PosX: 32, PosY: 1},
|
||
{NodeID: "dragons_lair.throne_ascension", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Throne Ascension", PosX: 33, PosY: 1},
|
||
{NodeID: "dragons_lair.molten_throne_approach", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Molten Throne Approach", PosX: 34, PosY: 1},
|
||
{NodeID: "dragons_lair.crown_steps", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Crown Steps", PosX: 35, PosY: 1},
|
||
{NodeID: "dragons_lair.final_step", Kind: NodeKindExploration, RegionID: r4,
|
||
Label: "Final Step", PosX: 36, PosY: 1},
|
||
{NodeID: "dragons_lair.boss", Kind: NodeKindBoss, IsBoss: true, RegionID: r4,
|
||
Label: "Infernax's Crown", PosX: 37, PosY: 1},
|
||
}
|
||
edges := []ZoneEdge{
|
||
// R1 preamble.
|
||
{From: "dragons_lair.entry", To: "dragons_lair.kobold_lookout", Lock: LockNone},
|
||
{From: "dragons_lair.kobold_lookout", To: "dragons_lair.kobold_warrens", Lock: LockNone},
|
||
{From: "dragons_lair.kobold_warrens", To: "dragons_lair.smoke_choked_hall", Lock: LockNone},
|
||
{From: "dragons_lair.smoke_choked_hall", To: "dragons_lair.ash_chapel", Lock: LockNone},
|
||
{From: "dragons_lair.ash_chapel", To: "dragons_lair.kobold_warchief_camp", Lock: LockNone},
|
||
{From: "dragons_lair.kobold_warchief_camp", To: "dragons_lair.ember_corridor", Lock: LockNone},
|
||
{From: "dragons_lair.ember_corridor", To: "dragons_lair.obsidian_steps", Lock: LockNone},
|
||
{From: "dragons_lair.obsidian_steps", To: "dragons_lair.cinder_passage", Lock: LockNone},
|
||
{From: "dragons_lair.cinder_passage", To: "dragons_lair.kobold_descent", Lock: LockNone},
|
||
|
||
// R1 → R2 transition.
|
||
{From: "dragons_lair.kobold_descent", To: "dragons_lair.drake_pens", Lock: LockNone},
|
||
|
||
// R2 buildup to fork1.
|
||
{From: "dragons_lair.drake_pens", To: "dragons_lair.drake_grooming_pit", Lock: LockNone},
|
||
{From: "dragons_lair.drake_grooming_pit", To: "dragons_lair.drake_holding_run", Lock: LockNone},
|
||
{From: "dragons_lair.drake_holding_run", To: "dragons_lair.drake_yards", Lock: LockNone},
|
||
{From: "dragons_lair.drake_yards", To: "dragons_lair.drake_handlers_hall", Lock: LockNone},
|
||
{From: "dragons_lair.drake_handlers_hall", To: "dragons_lair.drake_armory", Lock: LockNone},
|
||
{From: "dragons_lair.drake_armory", To: "dragons_lair.fork1", Lock: LockNone},
|
||
|
||
// Fork1 — binary, converges at wyrmlings_nest (R3 boundary).
|
||
{From: "dragons_lair.fork1", To: "dragons_lair.ash_bridge", Lock: LockNone, Weight: 1},
|
||
{From: "dragons_lair.fork1", To: "dragons_lair.treasure_vault",
|
||
Lock: LockPerception, LockData: map[string]any{"dc": 15},
|
||
Hint: "a draft from a side passage — and the dull glint of gold beyond it", Weight: 2},
|
||
|
||
// ash_bridge spur.
|
||
{From: "dragons_lair.ash_bridge", To: "dragons_lair.burning_span", Lock: LockNone},
|
||
{From: "dragons_lair.burning_span", To: "dragons_lair.cinder_walk", Lock: LockNone},
|
||
{From: "dragons_lair.cinder_walk", To: "dragons_lair.wyrmlings_nest", Lock: LockNone},
|
||
|
||
// treasure_vault spur.
|
||
{From: "dragons_lair.treasure_vault", To: "dragons_lair.coin_strewn_hall", Lock: LockNone},
|
||
{From: "dragons_lair.coin_strewn_hall", To: "dragons_lair.vault_passage", Lock: LockNone},
|
||
{From: "dragons_lair.vault_passage", To: "dragons_lair.wyrmlings_nest", Lock: LockNone},
|
||
|
||
// R3 the_vault buildup to fork2.
|
||
{From: "dragons_lair.wyrmlings_nest", To: "dragons_lair.hoard_outer", Lock: LockNone},
|
||
{From: "dragons_lair.hoard_outer", To: "dragons_lair.coin_river", Lock: LockNone},
|
||
{From: "dragons_lair.coin_river", To: "dragons_lair.hoard_arch", Lock: LockNone},
|
||
{From: "dragons_lair.hoard_arch", To: "dragons_lair.flame_corridor", Lock: LockNone},
|
||
{From: "dragons_lair.flame_corridor", To: "dragons_lair.infernax_threshold", Lock: LockNone},
|
||
{From: "dragons_lair.infernax_threshold", To: "dragons_lair.molten_steps", Lock: LockNone},
|
||
{From: "dragons_lair.molten_steps", To: "dragons_lair.inferno_walk", Lock: LockNone},
|
||
{From: "dragons_lair.inferno_walk", To: "dragons_lair.fork2", Lock: LockNone},
|
||
|
||
// Fork2 — capstone 3-way (R3 → R4).
|
||
{From: "dragons_lair.fork2", To: "dragons_lair.charge_walk", Lock: LockNone, Weight: 1},
|
||
{From: "dragons_lair.fork2", To: "dragons_lair.speakers_step",
|
||
Lock: LockStatCheck, LockData: map[string]any{"stat": "CHA", "dc": 16},
|
||
Hint: "Infernax speaks. The voice fills the chamber. You could speak back.", Weight: 2},
|
||
{From: "dragons_lair.fork2", To: "dragons_lair.hidden_passage",
|
||
Lock: LockPerception, LockData: map[string]any{"dc": 17},
|
||
Hint: "a single coin standing on edge — a draft, not a tremor", Weight: 3},
|
||
|
||
// direct_confrontation spur → merge.
|
||
{From: "dragons_lair.charge_walk", To: "dragons_lair.direct_confrontation", Lock: LockNone},
|
||
{From: "dragons_lair.direct_confrontation", To: "dragons_lair.broken_chamber", Lock: LockNone},
|
||
{From: "dragons_lair.broken_chamber", To: "dragons_lair.infernax_doors", Lock: LockNone},
|
||
|
||
// dragon_bargain spur → merge.
|
||
{From: "dragons_lair.speakers_step", To: "dragons_lair.dragon_bargain", Lock: LockNone},
|
||
{From: "dragons_lair.dragon_bargain", To: "dragons_lair.audience_hall", Lock: LockNone},
|
||
{From: "dragons_lair.audience_hall", To: "dragons_lair.infernax_doors", Lock: LockNone},
|
||
|
||
// hoard_pillar spur → merge.
|
||
{From: "dragons_lair.hidden_passage", To: "dragons_lair.hoard_pillar", Lock: LockNone},
|
||
{From: "dragons_lair.hoard_pillar", To: "dragons_lair.secret_arch", Lock: LockNone},
|
||
{From: "dragons_lair.secret_arch", To: "dragons_lair.infernax_doors", Lock: LockNone},
|
||
|
||
// R4 final approach.
|
||
{From: "dragons_lair.infernax_doors", To: "dragons_lair.throne_ascension", Lock: LockNone},
|
||
{From: "dragons_lair.throne_ascension", To: "dragons_lair.molten_throne_approach", Lock: LockNone},
|
||
{From: "dragons_lair.molten_throne_approach", To: "dragons_lair.crown_steps", Lock: LockNone},
|
||
{From: "dragons_lair.crown_steps", To: "dragons_lair.final_step", Lock: LockNone},
|
||
{From: "dragons_lair.final_step", To: "dragons_lair.boss", Lock: LockNone},
|
||
}
|
||
return BuildGraph(ZoneDragonsLair, nodes, edges)
|
||
}
|
||
|
||
func init() {
|
||
registerZoneGraph(zoneDragonsLairGraph())
|
||
}
|