mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 17:02:42 +00:00
adventure: Tier 6 postgame "Mythic" dungeons (P1–P7 calibration)
Five post-game dungeons above the T5 ceiling, gated on both T5 bosses beaten + level 18. Opt-in endgame: deadly solo, clearable by a party with Pete + pets. - P1 gating: postgameUnlocked (T5 clears + level floor), zonesForLevel excludes T6 unconditionally; wired into startZoneRun, !zone/!expedition, party accept, boredom picker, and the list dividers. - P2 bestiary: 15 elites + 5 signature bosses (Layer-1 stat blocks). - P3 zone defs + 4-region registries; ZoneLootEntry.BossOnly. - P4 five zone graphs on a shared builder (44–52 rooms, no soft-lock; Ossuary secret Verse nodes). - P5 loot: BossOnly enforced; signature items are real registry magic items; five Thom pity recipes off the per-zone crafting anchors. - P6 narration/flavor (5 files), T6 achievements, Pete stays zone-parametric. - P7 (in progress): sim can now reach gated T6 (SimRunner.SeedPostgameUnlock + IsPostgameZone). First calibration pass on millenia — hardened ossuary + drowned_star, softened first_hoard + unplace; last_meridian in band. Fix: party members were refused from every T6 zone because expeditionCmdAccept ran the level gate (which excludes T6) before the postgame check — the intended party endgame was unreachable. Route T6 through postgameUnlocked. Regression tests added.
This commit is contained in:
@@ -98,6 +98,75 @@ func TestSeatParty_RefusedFollowerHaltsTheRun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Regression: a party could never seat a follower into a Tier 6 post-game
|
||||
// zone. expeditionCmdAccept ran the plain zoneOpenToLevel level gate first, and
|
||||
// T6 zones are excluded from zonesForLevel by design, so every unlocked member
|
||||
// was refused ("beyond you for now") before the post-game check could admit
|
||||
// them. The intended party-only endgame was unreachable. The fix routes T6
|
||||
// zones through postgameUnlocked instead of the level gate.
|
||||
func TestSeatParty_PostgameZoneSeatsUnlockedMembers(t *testing.T) {
|
||||
setupZoneRunTestDB(t)
|
||||
s := newPartySimRunner()
|
||||
|
||||
leader := id.UserID("@sim-pg-ok:example")
|
||||
member := id.UserID("@sim-pg-ok-m1:example")
|
||||
for _, u := range []id.UserID{leader, member} {
|
||||
if _, err := s.BuildCharacter(u, ClassFighter, postgameLevelFloor+2); err != nil {
|
||||
t.Fatalf("build %s: %v", u, err)
|
||||
}
|
||||
s.Euro.Credit(u, 50000, "test")
|
||||
if err := s.SeedPostgameUnlock(u); err != nil {
|
||||
t.Fatalf("seed unlock %s: %v", u, err)
|
||||
}
|
||||
}
|
||||
defer cleanupExpeditions(leader)
|
||||
|
||||
ctx := MessageContext{Sender: leader}
|
||||
if err := s.P.handleDnDExpeditionCmd(ctx, "start "+string(ZoneOssuaryAscendant)+" heavy"); err != nil {
|
||||
t.Fatalf("start T6 expedition: %v", err)
|
||||
}
|
||||
if err := s.seatParty(leader, []id.UserID{member}); err != nil {
|
||||
t.Fatalf("seatParty refused an unlocked member into a T6 zone: %v", err)
|
||||
}
|
||||
exp, _ := getActiveExpedition(leader)
|
||||
if exp == nil {
|
||||
t.Fatal("expedition vanished while seating")
|
||||
}
|
||||
if size, err := partySize(exp.ID); err != nil || size != 2 {
|
||||
t.Fatalf("roster = %d (%v), want 2 (leader + 1 unlocked member)", size, err)
|
||||
}
|
||||
}
|
||||
|
||||
// The post-game gate must still refuse a member who has NOT beaten both T5
|
||||
// bosses, even in a party: no smuggling an ungated seat into a T6 zone. The
|
||||
// leader is unlocked (so the expedition starts); the member is not.
|
||||
func TestSeatParty_PostgameZoneRefusesUngatedMember(t *testing.T) {
|
||||
setupZoneRunTestDB(t)
|
||||
s := newPartySimRunner()
|
||||
|
||||
leader := id.UserID("@sim-pg-gate:example")
|
||||
member := id.UserID("@sim-pg-gate-m1:example")
|
||||
for _, u := range []id.UserID{leader, member} {
|
||||
if _, err := s.BuildCharacter(u, ClassFighter, postgameLevelFloor+2); err != nil {
|
||||
t.Fatalf("build %s: %v", u, err)
|
||||
}
|
||||
s.Euro.Credit(u, 50000, "test")
|
||||
}
|
||||
// Only the leader clears the T5 gate; the member stays ungated.
|
||||
if err := s.SeedPostgameUnlock(leader); err != nil {
|
||||
t.Fatalf("seed leader unlock: %v", err)
|
||||
}
|
||||
defer cleanupExpeditions(leader)
|
||||
|
||||
ctx := MessageContext{Sender: leader}
|
||||
if err := s.P.handleDnDExpeditionCmd(ctx, "start "+string(ZoneOssuaryAscendant)+" heavy"); err != nil {
|
||||
t.Fatalf("start T6 expedition: %v", err)
|
||||
}
|
||||
if err := s.seatParty(leader, []id.UserID{member}); err == nil {
|
||||
t.Fatal("seatParty admitted an ungated member into a T6 zone")
|
||||
}
|
||||
}
|
||||
|
||||
// A misspelled -class / -party-classes token used to build a character at the
|
||||
// unknown-class fallback — 1 HP — and the run reported a perfectly normal
|
||||
// outcome for it. Nothing downstream treats an unknown class as an error, so
|
||||
|
||||
Reference in New Issue
Block a user