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
Both T4 zones follow the D1-a/b/c pattern: extend the graph so the
longest entry→boss walk lands in band, keep the zone's authored
topology intact.
underdark (T4): 10 → 46 nodes, longest 30 (band 28-34)
feywild_crossing (T4): 9 → 53 nodes, longest 30 (band 28-34)
Topology preserved per zone:
- Underdark: 4-region authoring intact (every node carries a valid
RegionID); 3-way fork1 (drow R2 / illithid R3 / deep_chasm R1)
with LockNone / LockPerception / LockStatCheck CON; deep_chasm
intentionally stays R1; all three arms converge at the R4
throne_approach merge. Adds the missing Trap anchor (Collapsed
Arch) in the R1 preamble.
- Feywild Crossing: woven forks intact — hag_circle still has two
incoming edges (one per first-stage path), time_eddy stays
grove-exclusive, illusion_garden stays marsh-exclusive, fork1
remains CHA-vs-Perception with no LockNone option. Adds the
missing Trap anchor (Cursed Thicket) and a fae_court MERGE so
the three second-stage endings converge into one final boss
approach instead of triplicating it.
MinRooms/MaxRooms re-pitched 8-10 → 28-34 so the dice fallback also
lands in band for any future graphless variant.
T5 zones (dragons_lair, abyss_portal) deferred to D1-e — those
need a RegionID backfill on top of the length extension since their
regionsByZone entries are not currently honored by their graphs.
T4 zone, ninth (final) authored graph. Shape: 3-way regional fork
where each arm crosses a different region boundary, all converging at
R4 (deep_throne). First and only authored zone where every node
carries a non-empty RegionID matching dnd_expedition_region.go.
Three arms from fork1:
- R1→R2 drow_patrol → drow_captain (elite)
- R1→R3 psionic_corridor → mind_flayer (elite)
- R1→R1 deep_chasm (CON-gated harvest, stays in surface_tunnels)
…all converging at R4 throne_approach → boss.
Exercises G6 fireGraphRegionTransition end-to-end: edges crossing
region boundaries fire the transition hook (updates Expedition
.CurrentRegion, marks region visited, writes a transit log entry)
without burning supplies or advancing the day, since the graph is
the run state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>