N5/D4: secret content pass — treasure-cache secret rooms + cross-zone keys

Secret rooms were dead content: every NodeKindSecret node silently
collapsed to a normal exploration fight and its authored LootBias
(1.5-3.0) was never read at runtime. D4 makes them what they read as —
no-combat treasure caches.

resolveRoom now diverts a secret node (keyed off the graph node, since
CurrentRoomType has already lost the kind) to resolveSecretRoom before
the RoomType switch — shared by manual !zone advance, !expedition run
autopilot, and the sim. Each secret pays a guaranteed journal page
(the D1a grant hook built "for secret rooms"), a LootBias-weighted
treasure roll (floored at elite weight), and a guaranteed zone-tier
consumable cache, with bespoke in-world discovery flavor.

Underdark was the only T2+ zone with no secret; added at throne_gallery
on the universal R4 tail (Lost Reliquary, Perception DC 17), merging to
throne_steps so it's length-neutral.

Two cross-zone keys: the Sunken Temple's Coral Reliquary grants a Sunken
Sigil that opens a Sealed Reliquary in Manor Blackspire; the Underforge's
Forge Vault grants an Underforge Seal that opens a Sealed Vault in the
Underdark. Keys are persistent inventory items matched against LockKey
key_id; grants are idempotent.

Graph validator + no-soft-lock pass on both touched graphs; combat golden
byte-identical; go build/vet/test green repo-wide.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
prosolis
2026-07-10 16:02:15 -07:00
parent 9b6c1ff9a4
commit c37c95a3e3
8 changed files with 454 additions and 16 deletions

View File

@@ -61,6 +61,15 @@ package plugin
// Garrison) and the multi-region transition a teeth-y interrupt.
// The deep_chasm spur stays anchor-light (harvest, no second trap/elite)
// so the CON-gated climb keeps its "denser loot, less fighting" identity.
//
// N5/D4 turns throne_gallery (R4 tail, which every arm passes through) into
// a fork with two secret spokes, both merging to throne_steps so either is
// length-neutral vs the inner-hall path:
// - Lost Reliquary — the Underdark's own perception-gated secret (DC 17).
// - Sealed Vault — the cross-zone key vault opened by an Underforge Seal
// (earned in the Underforge's Forge Vault). LockKey "underforge seal".
// Placed on the universal R4 tail, not in an arm, so the secrets are
// reachable no matter which region the walk took.
func zoneUnderdarkGraph() ZoneGraph {
r1 := "underdark_surface_tunnels"
@@ -163,10 +172,22 @@ func zoneUnderdarkGraph() ZoneGraph {
Label: "Guard Post", PosX: 24, PosY: 2},
{NodeID: "underdark.throne_doors", Kind: NodeKindExploration, RegionID: r4,
Label: "Riven Doors", PosX: 25, PosY: 2},
{NodeID: "underdark.throne_gallery", Kind: NodeKindExploration, RegionID: r4,
{NodeID: "underdark.throne_gallery", Kind: NodeKindFork, RegionID: r4,
Label: "Throne Gallery", PosX: 26, PosY: 2},
{NodeID: "underdark.throne_inner_hall", Kind: NodeKindExploration, RegionID: r4,
Label: "Inner Hall", PosX: 27, PosY: 2},
// N5/D4 secret spokes off the throne gallery (both merge to
// throne_steps, so either detour is length-neutral vs the inner-hall
// path). lost_reliquary is the Underdark's own perception-gated secret;
// sealed_forge_vault is the cross-zone key vault opened by an Underforge
// Seal (earned in the Underforge's Forge Vault).
{NodeID: "underdark.lost_reliquary", Kind: NodeKindSecret, RegionID: r4,
Label: "Lost Reliquary", PosX: 27, PosY: 0,
Content: ZoneNodeContent{LootBias: 2.0}},
{NodeID: "underdark.sealed_forge_vault", Kind: NodeKindSecret, RegionID: r4,
Label: "Sealed Vault", PosX: 27, PosY: 4,
Content: ZoneNodeContent{LootBias: 2.2}},
{NodeID: "underdark.throne_steps", Kind: NodeKindExploration, RegionID: r4,
Label: "Throne Steps", PosX: 28, PosY: 2},
{NodeID: "underdark.boss", Kind: NodeKindBoss, IsBoss: true, RegionID: r4,
@@ -232,7 +253,15 @@ func zoneUnderdarkGraph() ZoneGraph {
{From: "underdark.throne_antechamber", To: "underdark.throne_guard_post", Lock: LockNone},
{From: "underdark.throne_guard_post", To: "underdark.throne_doors", Lock: LockNone},
{From: "underdark.throne_doors", To: "underdark.throne_gallery", Lock: LockNone},
{From: "underdark.throne_gallery", To: "underdark.throne_inner_hall", Lock: LockNone},
{From: "underdark.throne_gallery", To: "underdark.throne_inner_hall", Lock: LockNone, Weight: 1},
{From: "underdark.throne_gallery", To: "underdark.lost_reliquary",
Lock: LockPerception, LockData: map[string]any{"dc": 17},
Hint: "a side-shrine still lit, off the gallery's blind end", Weight: 2},
{From: "underdark.throne_gallery", To: "underdark.sealed_forge_vault",
Lock: LockKey, LockData: map[string]any{"key_id": "underforge seal"},
Hint: "a deep-road door branded by a forge you may have passed", Weight: 3},
{From: "underdark.lost_reliquary", To: "underdark.throne_steps", Lock: LockNone},
{From: "underdark.sealed_forge_vault", To: "underdark.throne_steps", Lock: LockNone},
{From: "underdark.throne_inner_hall", To: "underdark.throne_steps", Lock: LockNone},
{From: "underdark.throne_steps", To: "underdark.boss", Lock: LockNone},
}