mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
J3 D8-f #2: T4 difficulty lift (leaders-define-band) + feywild fork1 soft-lock fix
T4 monster tuning so martial leaders land in the 60-75% band (underdark 59/80, feywild 65/84 at L10/L12, n=50); casters trail (class-side gap that monster tuning provably can't compress -- Pass 1 showed casters pinned at 0% while martials moved). T5 deferred (walls everyone at its L12 floor; needs an L15-16 corpus). - dnd_bestiary.go: underdark elite/boss HP+AC up + caster-lethal proc cuts (mind_flayer/drow_mage/roper); feywild HP+AC up. - bestiary_srd.go: feywild multiattack profiles (fomorian/night_hag/green_hag) + Thornmother 2->3 lashes -- HP/AC alone didn't move feywild's low-damage roster; multiattack is what pulls facerolling martials into band. - zone_graph_feywild_crossing.go: free fork1's marsh edge. fork1 was the only fork in the game with every exit skill-locked (CHA+Perception, no LockNone) and deterministic no-retry rolls -- a prod SOFT-LOCK stranding ~60% of players (low CHA+WIS). Kept grove's CHA bargain as a bonus route. - expedition_sim.go: firstUnlockedForkChoice -- sim fork policy picks the first unlocked option instead of blind 'go 1' (which looped forever on locked forks); halts fork_all_locked if none. - tests: graph-wide TestZoneGraphs_NoSoftLockedFork + fork1 free-path guard. Writeup: sim_results/d8f_findings.md
This commit is contained in:
@@ -190,6 +190,40 @@ func TestCompileLegacyZoneGraph_AllRegistered(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestZoneGraphs_NoSoftLockedFork guards the invariant that every fork
|
||||
// node offers at least one traversable (LockNone) exit. A fork whose
|
||||
// every edge is skill-locked strands any player who fails all the checks
|
||||
// — fork rolls are deterministic with no retry. D8-f part 2 found
|
||||
// feywild fork1 violating this (it stranded ~60% of runs). Catch any
|
||||
// future author who locks every exit of a fork.
|
||||
func TestZoneGraphs_NoSoftLockedFork(t *testing.T) {
|
||||
for _, z := range allZones() {
|
||||
g, ok := loadZoneGraph(z.ID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for nodeID, node := range g.Nodes {
|
||||
if node.Kind != NodeKindFork {
|
||||
continue
|
||||
}
|
||||
outs := g.outgoingEdges(nodeID)
|
||||
if len(outs) == 0 {
|
||||
continue
|
||||
}
|
||||
free := false
|
||||
for _, e := range outs {
|
||||
if e.Lock == LockNone || e.Lock == "" {
|
||||
free = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !free {
|
||||
t.Errorf("zone %q fork %q: every exit is locked — soft-lock (a player failing all checks is stranded; add a LockNone fallback)", z.ID, nodeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveLegacyNodeID_StableShape(t *testing.T) {
|
||||
got := deriveLegacyNodeID("crypt_valdris", 0)
|
||||
want := "crypt_valdris.r1"
|
||||
|
||||
Reference in New Issue
Block a user