J3 D10: T4/T5 anchor-room variety (2nd elite + trap per zone)

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.
This commit is contained in:
prosolis
2026-05-28 20:00:09 -07:00
parent 4934383a9a
commit d80b437525
8 changed files with 166 additions and 16 deletions

View File

@@ -93,6 +93,8 @@ func TestFeywildCrossingGraph_FirstCHALock(t *testing.T) {
// TestFeywildCrossingGraph_TrapAnchor verifies D1-d added the missing
// Trap node. Original G8f graph had elite/secret but no trap.
// TestFeywildCrossingGraph_TrapAnchor verifies the preamble trap plus
// the D10 marsh-branch trap (Mire Steps).
func TestFeywildCrossingGraph_TrapAnchor(t *testing.T) {
g := zoneFeywildCrossingGraph()
var trapCount int
@@ -101,8 +103,30 @@ func TestFeywildCrossingGraph_TrapAnchor(t *testing.T) {
trapCount++
}
}
if trapCount != 1 {
t.Errorf("trap nodes = %d, want 1", trapCount)
if trapCount != 2 {
t.Errorf("trap nodes = %d, want 2 (cursed_thicket + D10 mire_steps)", trapCount)
}
if g.Nodes["feywild_crossing.mire_steps"].Kind != NodeKindTrap {
t.Error("D10: mire_steps (marsh branch) should be a Trap")
}
}
// TestFeywildCrossingGraph_EliteAnchors verifies the shared hag_circle
// elite plus the D10 grove-branch elite (Singing Orchard), so the first
// fork carries a per-branch anchor (grove=elite, marsh=trap).
func TestFeywildCrossingGraph_EliteAnchors(t *testing.T) {
g := zoneFeywildCrossingGraph()
var eliteCount int
for _, n := range g.Nodes {
if n.Kind == NodeKindElite {
eliteCount++
}
}
if eliteCount != 2 {
t.Errorf("elite nodes = %d, want 2 (hag_circle + D10 singing_orchard)", eliteCount)
}
if g.Nodes["feywild_crossing.singing_orchard"].Kind != NodeKindElite {
t.Error("D10: singing_orchard (grove branch) should be an Elite")
}
}