# Theriapolis — Phase 7 — Design & Implementation Plan
## Dungeons, Points of Interest, Room Templates, Loot, and the Dialogue → Combat Handoff

**Status:** Proposed (rewritten 2026-04-29 to reflect actual post-Phase-6.5 baseline).
Targets the codebase state as of **2026-04-28**:
Phase 6 + Phase 6.5 complete; 256×256 world; `ENABLE_RAIL=false`;
**SAVE_SCHEMA_VERSION=7**; **640 tests green**; levelling, subclass
selection, hybrid characters, passing detection, per-NPC scent tags,
and betrayal cascades all live.

**This document supersedes the 2026-04-27 draft of the Phase 7 plan**,
which was authored against a pre-6.5 baseline (SAVE=v6, no levelling,
spawn_npc/despawn_npc still stubs, BuildingDelta unemitted). That
draft's body remains useful as design intent and is preserved verbatim
in section 13 ("Archived prior draft") of the prior file's history;
this rewrite re-states the contract against the *actual* shipped
state, reconciles the Phase 6.5 deviations recorded in
`theriapolis-rpg-implementation-plan-phase6-5.md` §11, and folds the
Phase-6.5 carryover items into the Phase 7 milestones where they
belong.

**Audience:** the agent who will land Phase 7. Read §2 (Phase 6.5
deviation reconciliation) before writing code so you know which
6.5 deviations are now ratified contract, which are getting
re-implemented, and which Phase 7 milestones are picking up.

**Governing docs:**
- `theriapolis-rpg-implementation-plan.md` §§ 6 (Stage 19 PoIPlacement),
  11 ("Phase 7 — Dungeons / PoIs"), 12 (binding hard rules)
- `theriapolis-rpg-procgen.md` Layer 5 ("Procedural Dungeons / Points
  of Interest" + "Modular Room Templates" + "Clade-Responsive Design")
  — authoritative for the five dungeon types and the room-graph
  algorithm
- `theriapolis-rpg-procgen-addendum-a.md` (linear-feature exclusion still
  binding — dungeons stamp into chunks but do not lay down rivers/roads/rail)
- `theriapolis-rpg-questline.md` Act I (Old Howl mine, Lacroix break-in,
  Briarstead workshop) and Act III (Slaughterhouse Raid — forward-compat
  reference only; not authored in Phase 7)
- `theriapolis-rpg-equipment.md` (loot + weapon/armor/scent-tech catalogue)
- `theriapolis-rpg-clades.md` (size + body-form rules driving
  clade-responsive movement penalties)
- `theriapolis-rpg-implementation-plan-phase4.md` §3.1 (coordinate model),
  §3.4 (chunk streaming model — dungeons share the camera + tactical-tile
  space contract)
- `theriapolis-rpg-implementation-plan-phase5.md` §3.4 (encounter
  lifecycle, `EncounterId`, mid-combat save), §4.4 (Resolver), §4.6
  (DangerZone)
- `theriapolis-rpg-implementation-plan-phase6.md` §3.2 (no-scene-swap
  doctrine for buildings — *Phase 7 is where the explicit exception
  lands: dungeons get a scene swap because they're bounded interiors*),
  §4.4 (quest engine — `spawn_npc`/`despawn_npc` are stubs we're
  upgrading), §11 (deviations)
- `theriapolis-rpg-implementation-plan-phase6-5.md` **§11** (the Phase
  6.5 deviation table — reconciled in this plan's §2)

**All hard rules from the original plan §12 remain in force.**
No MonoGame in `Theriapolis.Core`, all RNG via `SeededRng`, all magic
numbers in `Constants.cs`, and the linear-feature exclusion /
determinism contracts from Phases 0–6.5.

---

## 1. Goals & non-goals

### Goals

1. **Dungeons that exist in the world.** The 100–200 Tier-5 PoIs already
   placed by Stage 19 (`PoIPlacementStage`) get **interiors**. Walking onto
   a PoI's entrance tile transitions into a bounded multi-room dungeon
   in tactical space; the player explores rooms, fights what's inside,
   loots, and walks back out the way they came in.
2. **Modular room templates.** Per `procgen.md` Layer 5: each dungeon
   type has 30–50 hand-authored room templates assembled procedurally
   into 3–20-room layouts. Phase 7 ships a starter library: ~30 for
   Imperium Ruin (the showcase type), ~10–15 each for the other four
   types. The room-graph algorithm is generic; adding more templates
   later is pure JSON authoring.
3. **One fully playable Imperium Ruin.** The master plan's exit
   criterion verbatim. A specific seed-anchored Imperium Ruin near the
   Act-I start area gets hand-tuned content: 8–10 rooms, a coherent
   environmental story (an ancient gladiator pit fallen feral),
   mid-tier loot, a final-room boss (or set-piece). This is the
   showcase. **Tuned for level 2-3** — assumes the levelling system
   that shipped in Phase 6.5 is in use; a level-1 PC is expected to
   either grind Old Howl + side encounters first or save-scum the boss.
4. **Loot you can pick up.** Tier-weighted random tables turn loot
   slots into `ItemInstance`s in chest decos. The existing Phase-5
   `LootTableDef` infrastructure (already loaded but currently consumed
   only on NPC death) extends to dungeon containers.
5. **Quest-driven NPC spawning is real.** Phase 6's `spawn_npc` /
   `despawn_npc` quest effects (which currently log-only) become live
   actor placements at world-coordinate or anchor targets. This is
   what unlocks Old Howl and Lacroix as **real tactical encounters**
   rather than narrative resolutions.
6. **Dialogue → combat handoff.** The hostile-NPC interaction the
   Phase-5/6 plumbing was waiting for: a dialogue option can close the
   conversation and push `CombatHUDScreen` with the NPC pre-set as
   hostile. Lacroix's "settle this here" branch at last has the
   payoff its content always implied.
7. **Old Howl mine ships as a real dungeon.** A small Abandoned-Mine PoI
   placed near Millhaven; 3–4 rooms; 3 brigand encounters; the
   Howl-stone heirloom in the deepest room. The Phase-6 narrative
   step (`give_item:howl_stone` on quest entry) is replaced with the
   actual delve. Proves the engine end-to-end against an existing
   Act-I quest.
8. **Lacroix climax is real.** The night-time break-in at Briarstead
   becomes a proper tactical encounter with the dialogue→combat
   handoff. Three branches preserved (kill / chase / interrogate);
   `kill` and `chase` resolve through combat, `interrogate` continues
   to resolve in-dialogue. The interrogate branch's "betrayal" path
   exercises the Phase 6.5 betrayal-cascade engine.
9. **Clade-responsive dungeon sizing.** Per `procgen.md` Layer 5
   final paragraph: Mustelid tunnels are tight, Ursid ruins are vast,
   etc. A `BuiltBy` tag on each room template + a size-vs-builder
   movement-cost helper bakes this into the gameplay surface, not
   just the visuals. Hybrid PCs use their **dominant-lineage** clade
   for size lookups (per the Phase 6.5 hybrid model).
10. **Phase 6.5 carryover wired.** The deviation table in
    `phase6-5.md` §11 named several items that "land when Phase 7
    surfaces them". This plan picks them up explicitly: scent-mask
    item-consumption, healing-potion Medical-Incompatibility scaling,
    auto-fire BetrayalCascade on `RepEventKind.Betrayal`, the
    PassingCheck first-meet wire-in, the HybridParentPicker UI, the
    `--level N` Tools flag, and the remaining 12 of 16 L3 subclass
    feature wirings. See §2 for the full reconciliation.
11. **Determinism preserved.** Same `(worldSeed, poiId)` → byte-identical
    dungeon layout, spawn list, and loot rolls. Save mid-dungeon, load,
    continue — byte-identical to the live session. Same contract as
    Phase 5 combat, Phase 6 dialogue, and Phase 6.5 levelling.
12. **Phase 0–6.5 invariants intact.** Polylines authoritative. Core
    stays MonoGame-free. All RNG via `SeededRng` with new named
    sub-streams declared in `Constants.cs`. Worldgen budget unchanged
    (dungeons generate lazily on first entry, not at worldgen time).

### Non-goals (explicit)

- **Acts II–V questline content.** Phase 10. The Slaughterhouse Raid
  (Act III), the Tunnel War cave-in (Act IV), Heartstone (Act V), and
  every other act-specific dungeon set-piece are explicitly *not*
  authored here. The engine that ships in Phase 7 must be capable of
  running them later — that's tested by ensuring the schema accepts
  larger room counts and multi-floor layouts — but the *content* is
  Phase 10.
- **Subclass feature wiring beyond L7.** Phase 6.5 shipped engine + 4
  of 16 L3 features. Phase 7 finishes L3 (12 more) and lands the
  combat-touching L7 features that the showcase content actually
  exercises (~5 features). L10 / L15 / L18 / L20 features stay
  scaffolded-but-not-wired; their content arrives in Acts II–V (Phase
  10) and Phase 9 polish.
- **Hybrid characters' deeper dialogue gating.** Phase 6.5 wired
  HybridBias + per-NPC discovery. Phase 7 surfaces the two
  still-unconsumed detriments (Illegible Body Language, Social Stigma)
  in the dialogue-prose layer, but only in scenes the Phase-7 narrative
  dungeons actually reach. The full multi-settlement gossip / hybrid
  reveal cascade is Phase 8 propagation work.
- **Per-NPC scent simulation as a propagating sim.** Phase 8.
  Phase 7's enemies are stat-block + behaviour NPCs; scent abilities
  read the per-NPC `ScentTags` introduced in Phase 6.5 M6. Cult Den
  dungeons' "scent-trace" environmental storytelling is *prose* in
  narrative rooms, not a sim.
- **NPC schedules / day-night activity.** Phase 8. Dungeon enemies
  occupy their rooms 24/7; the Lacroix encounter's "night-time"
  framing is a `WorldClock`-gated trigger condition, not a behaviour
  schedule on the NPC.
- **Long/short rest mechanics.** Phase 8. "Camping in a dungeon" is
  not a Phase-7 mechanic; the player rests by exiting and walking back
  to a settlement. Phase 6.5's "every level-up = full reset" and
  "per-encounter pool refresh" model continues.
- **Trap disarmament as a deep skill subsystem.** Phase 7 ships *one*
  trap kind (tripwire), one disarm interaction (DEX check), and one
  damage type (1d6 piercing). Pressure plates, magic runes, alchemy
  traps, gas chambers, etc. — Phase 8 polish or content-pack work.
- **Procedural side-quest generator.** Phase 6 §10 listed this as
  Phase 7 work but it duplicates the dungeon engine without adding
  new content. The infrastructure (anchor → role → quest template) is
  stubbed for Phase 8; for Phase 7 every quest is hand-authored.
- **Lockpicking + key system as a deep subsystem.** Phase 7 ships
  locked doors with a binary key-or-lockpick check; lock difficulty
  tiers, lockpick item consumption, crafting lockpicks — all defer
  to Phase 8 / 9.
- **Multi-floor dungeons as a UI feature.** Slaughterhouse-Raid-style
  multi-level dungeons need a stairway scene-swap chain. The schema
  supports it (a dungeon can have child dungeons), but no Phase-7
  PoI uses it. Imperium Ruin showcase is single-level.
- **Random encounter "wandering monsters".** Each room's spawn list
  is fixed at dungeon-generation time. No re-spawning, no wandering.
- **Light/torch mechanics.** Dungeons render at full visibility in
  Phase 7. Fog-of-war / torch radius is Phase 8 polish.
- **Faction quest lines.** Phase 10. Cult Den enemies are tagged with
  faction allegiance for forward-compat (a Thorn Council Cult Den
  contributes to Thorn standing on clear), but no faction-quest gate
  on the cleared state.
- **Time-based scent-mask expiry.** Phase 6.5 carried this as a
  Phase-8 dependency (clock-driven). Phase 7 ships scent-mask
  consumption with a permanent-until-replaced mask tier; Phase 8 adds
  the time-based decay.

---

## 2. Phase 6.5 deviation reconciliation

Phase 6.5 shipped with a deviation table at §11 of its plan. Each
entry below names a Phase 6.5 deviation and the Phase 7 disposition:

- **Ratify** — accept the deviation as the new contract; Phase 7 builds
  on the actually-shipped behaviour and the plan-as-written is
  archival reference only.
- **Re-implement** — undo the deviation, ship the original plan
  shape during Phase 7. (Used sparingly — Phase 6.5 deviations are
  generally well-reasoned.)
- **Extend** — accept the shipped state but pick up the deferred
  follow-up work as a Phase 7 milestone item.

### M0 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| New `XP_FOR_LEVEL[]` constant | Reused existing `XpTable.Threshold` | **Ratify.** Avoiding duplication is correct; the shipped accessor is canonical. |
| `--level N` Tools flag for `character-roll` | Not shipped | **Extend.** Phase 7 M0 picks this up. Dungeon-balance testing benefits from headless leveled-character generation, especially for the Imperium Ruin showcase tuning. |

### M1 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| Wire **Mark of the Oath** (made-up name) | Wired **Lay on Paws** (canonical L1 Covenant-Keeper feature) | **Ratify.** The JSON id is canonical. The plan's "Mark of the Oath" was a design-doc fiction. |
| Frightened-attacker disadvantage at M1 | Landed at M3 alongside Pheromone Fear | **Ratify.** Sequencing change only; the wiring is in. |
| `nose_for_lies`, `polyglot`, `covenant_sense` (passive flavour features) wired mechanically | Not wired | **Extend.** Phase 7's dialogue→combat handoff and the InteractionScreen scent-overlay are the natural surfaces. M4 of Phase 7 picks up `polyglot` (literacy gating in dialogue prose); `covenant_sense` and `nose_for_lies` get a tag-render hook in M2 (passes through `ScentOverlayPanel`'s extension points). |

### M2 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| All 24 subclasses' L3 features wired | Engine + **4 of 16** subclasses wired (Lone Fang, Herd-Wall, Pack-Forged, Blood Memory). 12 still scaffolded-only. | **Extend.** Phase 7 M0/M1 wires the remaining 12 — each is one switch case in `FeatureProcessor` plus 4–6 unit tests, mirroring the patterns the four shipped subclasses establish. The Imperium Ruin showcase exercises at least one feature from each class. |
| All combat-touching L7/L10/L15 features wired | 0 wired | **Extend (partially).** Phase 7 wires the **L7 combat-touching features** (~5 features per the showcase content). L10/L15 features stay scaffolded-only — their content arrives Acts II–V (Phase 10) and Phase 9 polish per the original 6.5 §10. |
| `SubclassResolver.Resolve(class, subclass) → IFeatureBundle` | Shipped as `UnlockedFeaturesAt(...)` | **Ratify.** The id-list lookup is the right abstraction. |

### M3 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| Pheromone Craft as bonus action emit (vs JSON's "short rest crafting" prose) | Shipped as plan version | **Ratify.** Crafting framing is Phase 8 polish. |
| Covenant Authority as one mechanic, not three | Shipped as single -2 attack penalty | **Ratify.** The other two options (Compel Truth, Shield the Innocent) are dialogue/subclass content that lands as authored material in Phase 9–10. |
| Per-level resource ladders, ladder verification tests | Shipped | **Ratify.** |
| `OathAttackPenalty` lazy expiry sweep | Shipped | **Ratify.** Phase 8's clock model can replace with proactive sweeps. |

### M4 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| `HybridDetrimentsDef` JSON loader | Implemented as code constants in `HybridDetriments.cs` | **Ratify.** Universal invariant rules don't need JSON drift. |
| Ability-mod blending = "take one from each parent clade" with player choice on collision | Shipped as **declarative blend** (apply both clades' + species' mod dictionaries, collisions accumulate) | **Ratify with playtest gate.** The Imperium Ruin showcase will be the first content where hybrid PC mechanical balance shows up clearly. If post-M3 playtest indicates the auto-accumulation is too generous or too stingy, the decision moves to "Extend" — ship the choice picker. Recorded as an open decision (§10.10). |
| `HybridParentPicker` Myra wizard step | Not shipped — data layer + builder API only | **Extend.** Phase 7 M0 ships the picker UI. The data plumbing all works through `CharacterBuilder.IsHybridOrigin / HybridSire* / HybridDam* / HybridDominantParent`; the screen extension is mechanical. |
| All four universal Hybrid detriments applied | Medical Incompatibility wired (Field Repair, Lay on Paws); Scent Dysphoria wired (M5 PassingCheck); **Illegible Body Language + Social Stigma exposed but unconsumed** | **Extend.** Phase 7 M4 wires Illegible Body Language (disadvantage on nonverbal CHA checks with purebred NPCs) and Social Stigma (-2 to first CHA check with strangers in non-progressive settlements) into the dialogue-prose layer. The hooks land alongside the dialogue→combat handoff work since both touch `DialogueRunner` evaluation. |
| Healing-potion path applies Medical Incompatibility | Not shipped (no consume-potion handler exists) | **Extend.** Phase 7 M2 ships a generic inventory-item-consumption handler as part of dungeon loot interaction. Healing potions and scent masks share the same code path. |

### M5 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| `PassingCheck.Roll` returns 7-outcome enum | Shipped | **Ratify.** |
| **PC-side `NpcsWhoKnow`** as authoritative source for `EffectiveDisposition` (vs NPC `MemoryFlags`) | Shipped — dual-write keeps disposition / ledger separable | **Ratify.** Architectural call; the dual-write is the right shape for save/load round-tripping. |
| `BiasProfileDef.HybridBias` consumed by `EffectiveDisposition` | Shipped | **Ratify.** |
| Scent-mask consumable handler | Not shipped — `ScentMaskTier` is static state, programmatic-only | **Extend.** Phase 7 M2 picks this up alongside the healing-potion consumption handler (one shared inventory-consume pipeline). |
| `PassingCheck.RollAndApply` wired into `InteractionScreen` first-meet | Not shipped | **Extend.** Phase 7 M4 wires this when extending `DialogueRunner` for the start_encounter effect — both edits land in the same file. |
| Military / Deep-Cover scent-mask items | Only `scent_mask_basic` exists | **Extend.** Phase 7 M2 adds `scent_mask_military` and `scent_mask_deep_cover` to `items.json` and threads them through dungeon loot tables. |
| Time-based mask expiry | Not shipped — Phase 8 work | **Defer.** Stays in Phase 8 (clock-driven simulation). |

### M6 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| `ScentTag` enum + per-NPC tag list | Shipped (7 faction-affiliation + 4 runtime-derived tags) | **Ratify.** |
| `npc_templates.json` extended with per-template `default_scent_tags` | Faction-affiliation tags **derived automatically** from existing `FactionId` | **Ratify.** Simpler, error-proof. Phase 7 documents the override path (per-template tag override field) but does not exercise it. |
| Combat hook for `HasRecentlyKilled` | Schema in place, Resolver doesn't set it | **Extend.** Phase 7 M5 wires `Resolver.AttemptAttack` to set `HasRecentlyKilled` on melee kills. The Imperium Ruin showcase's multi-room combat is the natural surface — kill in one room, walk to the next, the NPC there scent-reads it. |

### M7 deviations

| Plan said | Shipped | Phase 7 disposition |
|---|---|---|
| Magnitude tier mapping vs raw values | Shipped as tier mapping | **Ratify.** Less brittle. |
| `RepEventKind.Betrayal` automatically triggers cascade | Shipped as **explicit caller-driven** `BetrayalCascade.Apply` | **Extend.** Phase 7 M4 wires automatic firing from quest-engine `rep_event` effects: when a quest effect emits `RepEventKind.Betrayal`, `QuestEngine.RunEffect` calls `BetrayalCascade.Apply` after the underlying `Submit`. Tests submitting synthetic events directly via `PlayerReputation.Submit` are unaffected — the auto-fire is at the `QuestEngine` layer, not the `PlayerReputation` layer. This preserves the deviation's purity argument while letting authored content trigger cascades automatically. |
| Patrol/guard permanent aggro flag survives save | Named NPCs re-acquire via `PersonalDisposition.Memory["betrayed_me"]` flag (which IS persisted); generic NPCs are chunk-ephemeral | **Ratify.** Consistent with chunk-ephemeral design; same pattern as M6's runtime scent flags. |

### Cross-cutting carryovers

The Phase 6.5 §11 cross-cutting carryover table named items "implicit
in the Phase 6.5 plan but explicitly belong to subsequent phases".
Phase 7 picks up the ones that block Phase 7 content from shipping:

| Carryover item | Phase 7 milestone |
|---|---|
| `--level N` Tools flag for `character-roll` | M0 |
| Remaining 12 of 16 subclass L3 features | M0 / M1 (interleaved per class) |
| Combat-touching L7 subclass features (~5) | M1 |
| HybridParentPicker Myra wizard step | M0 |
| Combat hook for `HasRecentlyKilled` | M5 |
| Scent-mask + healing-potion item-consumption handler | M2 (one shared pipeline) |
| Military + Deep-Cover scent-mask items in `items.json` | M2 (loot-table content) |
| Healing-potion consumption + Medical Incompatibility on potions | M2 (loot-table content + consume handler) |
| Auto-fire `BetrayalCascade` from quest-engine `rep_event` effects | M4 |
| `PassingCheck.RollAndApply` wired into `InteractionScreen` first-meet | M4 |
| Illegible Body Language + Social Stigma in dialogue prose | M4 |
| Time-based mask expiry | **Stays in Phase 8** (not Phase 7) |
| Long/short rest model | **Stays in Phase 8** |

---

## 3. Current-state inventory (what we plug into)

Audited 2026-04-28 against the post-6.5 codebase:

| Piece | Where | Phase 7 use |
|---|---|---|
| `PoiType` enum + `Settlement.IsPoi` / `Settlement.PoiType` | [Settlement.cs:19](Theriapolis.Core/World/Settlement.cs) | Source of truth for which dungeon type to generate per PoI. All 5 types already named: `ImperiumRuin`, `AbandonedMine`, `CultDen`, `NaturalCave`, `OvergrownSettlement`. |
| `PoIPlacementStage` (Stage 19) | [PoIPlacementStage.cs](Theriapolis.Core/World/Generation/Stages/PoIPlacementStage.cs) | Already places PoIs with biome-driven `PoiType` selection. Phase 7 *consumes* this; only minor extensions (deterministic level-band tag per PoI; narrative-anchor hint for Old Howl + Imperium showcase) needed. |
| `Settlement.Buildings` + `BuildingFootprint` | [BuildingFootprint.cs](Theriapolis.Core/World/Settlements/BuildingFootprint.cs) | Reference design for `Dungeon.Rooms` + `RoomFootprint` — same pattern (id + AABB + template id), one level deeper. |
| `SettlementStamper` | [SettlementStamper.cs](Theriapolis.Core/World/Settlements/SettlementStamper.cs) | Reference for how to stamp a complex tile-array structure deterministically; `DungeonGenerator` mirrors its shape but emits a self-contained `Dungeon` rather than chunk overlays. |
| `TacticalChunkGen` 5-pass pipeline | [TacticalChunkGen.cs](Theriapolis.Core/Tactical/TacticalChunkGen.cs) | Phase 7 adds: a sixth pass (`Pass6_PoiEntrance`) that stamps a single entrance-tile deco onto the PoI's surface chunk. The dungeon itself lives outside the chunk pipeline. |
| `SpawnKind.PoiGuard` | [TacticalChunk.cs:86](Theriapolis.Core/Tactical/TacticalChunk.cs) | Already in the spawn-kind enum but never *placed* by `TacticalChunkGen`. Phase 7 promotes it: dungeon generators emit `SpawnKind.PoiGuard` per occupied room. |
| `LootTableDef` + `loot_tables.json` | [LootTableDef.cs](Theriapolis.Core/Data/LootTableDef.cs), [loot_tables.json](Content/Data/loot_tables.json) | Phase 5 infrastructure consumed only on NPC death today. Phase 7 wires it to dungeon containers via `LootGenerator.RollContainer(tableId, dungeonSeed, slotIdx)`. |
| `QuestEngine` + 12 trigger / 11 effect kinds | [QuestEngine.cs](Theriapolis.Core/Rules/Quests/QuestEngine.cs) | `spawn_npc` / `despawn_npc` currently log-only ([QuestEngine.cs:294](Theriapolis.Core/Rules/Quests/QuestEngine.cs)). Phase 7 makes them real. Also wires `BetrayalCascade.Apply` into `RunEffect` when an effect emits `RepEventKind.Betrayal` (deviation extension from 6.5 M7). |
| `QuestState`, `QuestSnapshot` | [QuestState.cs](Theriapolis.Core/Rules/Quests/QuestState.cs) | Unchanged. Phase 7 only adds new effect kinds, not new state. |
| `BetrayalCascade.Apply` | (Phase 6.5 M7) | Already shipped as caller-driven. Phase 7 wires it into `QuestEngine.RunEffect` per the M7 deviation extension. |
| `PassingCheck.Roll` / `RollAndApply` | (Phase 6.5 M5) | Already shipped programmatically. Phase 7 wires `RollAndApply` into `InteractionScreen.OnOpen` first-meet path per the M5 deviation extension. |
| `Hybrid.NpcsWhoKnow` set + `KnowsPlayerIsHybrid` per-NPC dual-write | (Phase 6.5 M5) | Already shipped. Phase 7's dialogue-prose extensions read `pc.IsHybrid && knows` exactly the way `EffectiveDisposition` does. |
| `Hybrid.ActiveMaskTier` | (Phase 6.5 M5) | Static state; Phase 7 M2 wires the inventory consume-mask handler that mutates it. |
| `Character.Level`, `Character.SubclassId`, `Character.LearnedFeatureIds` | (Phase 6.5 M0/M2) | Phase 7 reads these for: dungeon scaling per `LevelBand`, subclass-feature gating in dungeon HUD, scent-mastery (`master_nose`) granting 3-tag scent reads. |
| `XpTable.Threshold` | (Phase 6.5 M0) | Phase 7 awards XP per killed dungeon NPC (already wired in 6.5 M0) and adds a dungeon-clear XP bonus on full clear. |
| `InteractionScreen` (dialogue UI) | [InteractionScreen.cs](Theriapolis.Game/Screens/InteractionScreen.cs) | Phase 7 adds: (a) handling for the new `start_encounter` dialogue effect; (b) the `PassingCheck.RollAndApply` first-meet wire-in (Phase 6.5 M5 carryover); (c) the Illegible Body Language / Social Stigma prose pip (Phase 6.5 M4 carryover). |
| `CombatHUDScreen` + `Encounter` | [CombatHUDScreen.cs](Theriapolis.Game/Screens/CombatHUDScreen.cs), [Encounter.cs](Theriapolis.Core/Rules/Combat/Encounter.cs) | Encounter creation already keyed by `EncounterId`. Phase 7 adds `Encounter.FromDialogueHandoff(npcId, playerId)` factory with stable `EncounterId` from `(seed, npcId)`. |
| `NpcInstantiator` | [NpcInstantiator.cs](Theriapolis.Core/Rules/Combat/NpcInstantiator.cs) | Phase 7 adds a per-dungeon-type override map (`spawn_kind_to_template_by_dungeon_type` in `npc_templates.json`). |
| `EncounterTrigger` | [EncounterTrigger.cs](Theriapolis.Core/Rules/Combat/EncounterTrigger.cs) | Phase 7 extends: while the active scene is a `DungeonScene`, hostile-LoS-trigger reads from the dungeon's room-local actor list. |
| `ChunkStreamer` | [ChunkStreamer.cs](Theriapolis.Core/Tactical/ChunkStreamer.cs) | Phase 7 *does not modify it*. Dungeons live outside chunk space. The streamer simply pauses (no eviction, no streaming) while a `DungeonScene` is active. |
| `IMapView` | [IMapView.cs](Theriapolis.Game/Rendering/IMapView.cs) | New implementation `DungeonRenderer` joins `WorldMapRenderer` and `TacticalRenderer` as the third active view. |
| `Camera2D` | [Camera2D.cs](Theriapolis.Game/Rendering/Camera2D.cs) | Reused unchanged. A dungeon's coordinate space is locally `[0..dungeon.WorldPixelW, 0..dungeon.WorldPixelH]`. |
| `WorldClock` | [WorldClock.cs](Theriapolis.Core/Time/WorldClock.cs) | Continues to advance during dungeon exploration (10 in-game seconds per tactical tile). The Lacroix night-time gate reads `WorldClock.Hour < 6 \|\| Hour >= 22`. |
| `SaveBody` (v7 — bumped by Phase 6.5) | [SaveBody.cs](Theriapolis.Core/Persistence/SaveBody.cs) | Phase 7 bumps to **v8**. Adds `Dungeons: List<DungeonStateSnapshot>`, `Buildings: List<BuildingDelta>`, `Anchors: AnchorRegistrySnapshot` (the latter two were reserved-but-empty pre-Phase 7). |
| `SaveCodec` reserved tags | [SaveCodec.cs:39-40](Theriapolis.Core/Persistence/SaveCodec.cs) | `TAG_ANCHORS=113` and `TAG_BUILDINGS=114` reserved comments still in code. Phase 7 promotes them to actually-emitted plus adds `TAG_DUNGEONS=115`. Phase 6.5's character extensions used the existing `TAG_CHARACTER=100` section's EOS-checked appends — no tag collision. |
| `SaveMigrations/V6ToV7Migration.cs` | (Phase 6.5) | Already shipped. Phase 7 adds **`V7ToV8Migration.cs`**, additive: empty defaults for `Dungeons`, `Buildings`, `Anchors`. |
| `SeededRng` | [SeededRng.cs](Theriapolis.Core/Util/SeededRng.cs) | Phase 7 adds new sub-streams: `RNG_DUNGEON_LAYOUT`, `RNG_ROOM_PICK`, `RNG_DUNGEON_POPULATE`, `RNG_DUNGEON_LOOT`. Existing `RNG_LOOT` (encounter drops) and `RNG_POI` (worldgen-time PoI placement) stay distinct, as do Phase 6.5's `RNG_LEVELUP` and `RNG_PASSING`. |
| `ContentLoader` / `ContentResolver` | [ContentLoader.cs](Theriapolis.Core/Data/ContentLoader.cs) | Add `LoadRoomTemplates` (recursive scan of `Content/Data/room_templates/<dungeon-type>/`), `LoadDungeonLayouts` (`Content/Data/dungeon_layouts/`). Mirrors the `LoadBuildingTemplates` / `LoadSettlementLayouts` pattern from Phase 6. |
| `ContentValidate` Tools command | [ContentValidate.cs](Theriapolis.Tools/Commands/ContentValidate.cs) | Extended: every room template's grid is valid; every dungeon layout references real templates; every loot table referenced by a layout exists; every npc template referenced by a per-dungeon-type spawn map exists. |
| `Theriapolis.Tools` | (project) | New commands: `dungeon-render`, `dungeon-walk`, `loot-distribution`. Plus the `--level N` flag on `character-roll` (Phase 6.5 M0 carryover). |
| `FeatureProcessor.cs` | (Phase 6.5 M2) | Phase 7 adds switch cases for the remaining 12 L3 subclass features and the ~5 L7 combat-touching features. Pattern is established by 6.5's 4 wired subclasses. |

Three facts that materially shape Phase 7:

- **PoIs already exist.** Stage 19 placed them. Phase 7 doesn't generate
  them at worldgen time — it generates *interiors* on demand, the first
  time the player crosses an entrance tile. This keeps the worldgen
  budget unchanged and naturally bounds memory: even at ~80 PoIs per
  256×256 world, only the ones the player visits ever spawn a `Dungeon`
  runtime object.
- **Two narrative dungeons (Old Howl, Lacroix break-in) already have
  Phase-6 quest content** that resolves narratively. Phase 7 *replaces*
  the narrative resolution with real combat at the same quest beats —
  the JSON edits are surgical, not rewrites.
- **Phase 6 explicitly punted on `BuildingDelta`** (the v7 reserved-
  but-empty save tag). Phase 7 needs it for the Lacroix break-in
  (door is broken during the encounter, persists post-combat) — so the
  delta type lands here.

---

## 4. Phase 7 architecture

### 4.1 Module layout

```
Theriapolis.Core/
  Dungeons/                                    NEW namespace
    Dungeon.cs                  class — runtime: PoiId, Type, Tiles[,], Rooms[], Connections[], EntranceTile, Spawns, LootContainers
    Room.cs                     class — runtime: Id, AABB, TemplateId, BuiltBy clade, Role (entry/loot/narrative/boss/dead-end), spawned NPCs, looted flags
    RoomConnection.cs           record — (roomA, doorPosA) ↔ (roomB, doorPosB); door state (open/closed/locked)
    DungeonGenerator.cs         static — deterministic: (worldSeed, poi) → Dungeon
    DungeonLayoutBuilder.cs     static — per dungeon type: room-count band, branching policy, special-room placement (entry, narrative, boss)
    RoomGraphAssembler.cs       static — graph of rooms with door-matching constraints; rejects unreachable layouts
    RoomTilePainter.cs          static — copies a `RoomTemplateDef` grid into the dungeon's tile array at the room's AABB
    DungeonScene.cs             class — wraps a live `Dungeon` as the active tile-source while the player is inside
    DungeonState.cs             class — serialisable mutable state: cleared rooms, opened doors, looted containers, killed NPC ids
    DungeonRegistry.cs          class — owned by PlayScreen; maps `PoiId → Dungeon` (live) and `PoiId → DungeonState` (persisted)
    LootGenerator.cs            static — `RollContainer(tableId, dungeonSeed, slotIdx) → ItemInstance[]`
    ClademorphicMovement.cs     static — `GetCostMultiplier(playerSize, room.BuiltBy) → float`
  Items/
    ConsumableHandler.cs        NEW — central dispatch for "consume this inventory item":
                                       healing potion → restore HP (with Hybrid Medical Incompatibility 0.75× scaling)
                                       scent_mask_basic / _military / _deep_cover → set Hybrid.ActiveMaskTier
                                       (other consumables route here as they're added)
  Data/
    RoomTemplateDef.cs          record — JSON-loaded; grid (chars), doors, deco placements, encounter slots, loot slots, BuiltBy clade, role-eligibility
    DungeonLayoutDef.cs         record — JSON-loaded; per-type rules (size band, room-count weights, branching, narrative-room policy)
    ContentLoader.cs            EXTEND — add `LoadRoomTemplates`, `LoadDungeonLayouts`
    ContentResolver.cs          EXTEND — `RoomTemplatesForType(PoiType)`, `LayoutForType(PoiType, sizeBand)`
  World/Generation/Stages/
    PoIPlacementStage.cs        EXTEND — assign per-PoI `LevelBand` (0..3) from distance-from-start + macro hostility
    PoIPlacementStage.cs        EXTEND — assign per-PoI `Anchor` for the *narrative* dungeons: Old Howl mine snaps near Millhaven; the Imperium Ruin showcase snaps to a specific Tier-5 site within Act-I travel range
  Tactical/
    TacticalChunkGen.cs         EXTEND — Pass6_PoiEntrance: stamps a `Stairs` deco at the world-pixel location of any PoI whose chunk overlaps. The deco is the player's interaction trigger.
    TacticalTile.cs             EXTEND — new `TacticalSurface`: DungeonFloor, DungeonRubble, DungeonTile (mosaic), Cave, MineFloor; new `TacticalDeco`: Stairs, DungeonDoor, Container, Trap, Brazier, Pillar, ImperiumStatue; new `TacticalFlags`: Dungeon, RoomBoundary, EntranceTile, ExitTile
  Rules/Combat/
    EncounterTrigger.cs         EXTEND — when active scene is `DungeonScene`, source actors from `_activeDungeon.Actors` not `ChunkStreamer`
    NpcInstantiator.cs          EXTEND — accept a `DungeonContext` parameter; consult `npc_templates.json`'s `spawn_kind_to_template_by_dungeon_type` table when the spawning chunk is a dungeon room
    Encounter.cs                EXTEND — new factory `FromDialogueHandoff(seed, npc, player) → Encounter` with stable `EncounterId` from `(seed, npc.Id)` so dialogue→combat is deterministic and savable
    Resolver.cs                 EXTEND — set `HasRecentlyKilled` scent-tag on melee kills (Phase 6.5 M6 carryover); read it at attack-time for narrative-prose surfacing
  Rules/Quests/
    QuestEngine.cs              EXTEND — `spawn_npc` resolves target (`anchor:` / `world_tile:` / `dungeon:` / `building_role:`) and calls `ActorManager.SpawnNpc`; `despawn_npc` resolves the same way and calls `ActorManager.RemoveActor`
    QuestEngine.cs              EXTEND — `rep_event` effect with `RepEventKind.Betrayal` auto-fires `BetrayalCascade.Apply` after `Submit` (Phase 6.5 M7 carryover)
    QuestContext.cs             EXTEND — add `DungeonRegistry`, `AnchorRegistry`, `ActorManager` for effect resolution
  Rules/Dialogue/
    DialogueRunner.cs           EXTEND — handle `start_encounter` effect kind: capture the active NPC, pop InteractionScreen, push CombatHUDScreen with the encounter
    DialogueRunner.cs           EXTEND — read `pc.IsHybrid && knows` to surface Illegible Body Language / Social Stigma prose pips (Phase 6.5 M4 carryover)
    DialogueDef.cs              EXTEND (record schema) — add the new effect kind to the loader's enum
  Rules/Character/
    FeatureProcessor.cs         EXTEND — switch cases for the remaining 12 L3 subclass features + ~5 L7 combat-touching features
  Persistence/
    SaveBody.cs                 EXTEND — bump SAVE_SCHEMA_VERSION to 8; emit `Dungeons: List<DungeonStateSnapshot>`, `Buildings: List<BuildingDelta>`, `Anchors: AnchorRegistrySnapshot`
    SaveCodec.cs                EXTEND — promote TAG_ANCHORS=113, TAG_BUILDINGS=114 from reserved to emitted; add TAG_DUNGEONS=115
    DungeonStateSnapshot.cs     class — serialisable: PoiId, ClearedRooms[], OpenedDoors[], LootedContainers[], KilledNpcIds[]
    BuildingDelta.cs            struct — chunkCoord + buildingId + door-broken flag + sign-vandalised flag
    AnchorRegistrySnapshot.cs   class — serialisable: anchor:* → SettlementId / NpcId map
    SaveMigrations/
      V7ToV8Migration.cs        NEW — additive: empty defaults for new lists
  Util/
    SeededRng.cs                — unchanged (sub-stream constants live in Constants.cs)

Theriapolis.Game/
  Screens/
    PlayScreen.cs               EXTEND — own `_dungeonRegistry`; on entrance-tile cross, `EnterDungeon(poiId)`; on exit-tile cross, `ExitDungeon()`
    InteractionScreen.cs        EXTEND — handle `start_encounter` dialogue effect; wire `PassingCheck.RollAndApply` first-meet hook; surface Illegible Body Language / Social Stigma pips
    CharacterCreationScreen.cs  EXTEND — Hybrid origin checkbox + `HybridParentPicker` Myra panel (Phase 6.5 M4 carryover)
    InventoryScreen.cs          EXTEND — "Use" button on consumables routes to `ConsumableHandler.Consume(itemId, pcChar)`
    DungeonClearScreen.cs       NEW — small modal shown on dungeon clear (XP bonus, loot summary, narrative coda)
  Rendering/
    DungeonRenderer.cs          NEW (IMapView) — reads the active `DungeonScene` and renders its tile array via the same atlas + sprite pipeline as the tactical renderer
  UI/
    HybridParentPicker.cs       NEW — Myra panel: side-by-side Sire (left) + Dam (right) clade-and-species pickers, dominant-lineage toggle, trait-split summary (Phase 6.5 M4 carryover)
  Input/
    PlayerController.cs         EXTEND — recognise the entrance-tile interact (E key) on a `Stairs` deco; recognise the door-interact (E key) on `DungeonDoor`; container-interact (E key) on `Container`

Theriapolis.Tools/Commands/
  CharacterRoll.cs              EXTEND — `--level N` flag (Phase 6.5 M0 carryover): rolls a level-N character via repeated LevelUpFlow application
  DungeonRender.cs              NEW — `dungeon-render --seed N --poi <id> --out d.png` and `--template <id>` mode for single-template render
  DungeonWalk.cs                NEW — `dungeon-walk --seed N --poi <id> [--steps M]` headless deterministic walkthrough
  LootDistribution.cs           NEW — `loot-distribution --table <id> --rolls 1000` histogram dump
  ContentValidate.cs            EXTEND — room-template grid validator + dungeon-layout reference validator + loot-table reference validator

Theriapolis.Tests/
  Dungeons/                                    NEW
    DungeonGeneratorDeterminismTests.cs   — same (seed, poiId) → byte-identical dungeon
    DungeonReachabilityTests.cs            — every room reachable from entrance via doors
    DungeonScaleTests.cs                   — small/medium/large bands within plan-spec room counts
    RoomTemplateValidationTests.cs         — every authored template is a valid grid
    DungeonClademorphicTests.cs            — Mustelid-built room + Large PC produces 1.5× movement cost; hybrid PC uses dominant lineage's size
    DungeonStateRoundTripTests.cs          — modify dungeon, save, load, state intact
    DungeonSceneSwapTests.cs               — enter/exit cleanly transitions actor + camera
    DungeonGeneratorBudgetTests.cs         — generation completes in <400ms even under retry-fallback
    LootGeneratorDeterminismTests.cs
  Quests/
    QuestSpawnNpcTests.cs                  — `spawn_npc` effect actually places an NPC
    QuestDespawnNpcTests.cs
    QuestBetrayalAutoFireTests.cs          — `rep_event:Betrayal` auto-fires the cascade (Phase 6.5 M7 carryover verification)
    OldHowlIntegrationTests.cs             — full Old Howl quest plays through to Howl-stone delivery at fixed seed
    LacroixIntegrationTests.cs             — full Lacroix climax plays through with combat, all 3 branches lead to expected end-state
  Combat/
    DialogueToCombatHandoffTests.cs        — start_encounter effect closes dialogue + opens combat with stable EncounterId
    DungeonEncounterDeterminismTests.cs    — same dungeon spawn list + same player input → identical combat outcome
    RecentlyKilledScentTagTests.cs         — Resolver melee kill sets HasRecentlyKilled (Phase 6.5 M6 carryover)
  Character/
    HybridParentPickerWizardTests.cs       — character creation through the picker produces same Character as programmatic TryBuildHybrid
    SubclassFeatureL3CompletionTests.cs    — every L3 subclass feature (16 of 16) wired and exercised
    SubclassFeatureL7CombatTests.cs        — the ~5 L7 combat-touching features wired
    HealingPotionMedicalIncompatibilityTests.cs — hybrid PC consuming a healing potion gets 0.75× scaling
  Items/
    ConsumableHandlerTests.cs              — scent_mask_basic/military/deep_cover route correctly; healing potions route correctly
  Dialogue/
    HybridSocialStigmaTests.cs             — first-CHA-stranger pip surfaces in dialogue prose for hybrid PCs in non-progressive settlements
    PassingCheckFirstMeetTests.cs          — InteractionScreen first-meet triggers RollAndApply
  Persistence/
    DungeonStateSaveRoundTripTests.cs
    BuildingDeltaSaveRoundTripTests.cs
    AnchorRegistrySaveRoundTripTests.cs
    V7ToV8MigrationTests.cs

Content/Data/
  room_templates/                              NEW
    imperium/                  ~30 templates (showcase)
      entry_grand_hall.json
      coliseum_corridor_short.json
      coliseum_corridor_long.json
      collapsed_arch.json
      pillar_room_cardinal.json
      pillar_room_diagonal.json
      sarcophagus_chamber.json
      mosaic_atrium.json
      narrative_audience_chamber.json
      boss_throne_room.json
      ... (~20 more)
    mine/                      ~12 templates
      entry_shaft.json
      tunnel_T.json
      tunnel_cross.json
      cave_in_blocked.json
      mineral_vein_room.json
      timbered_gallery.json
      narrative_collapse_site.json
      ... (~5 more)
    cult/                      ~10 templates
    cave/                      ~10 templates
    overgrown/                 ~10 templates
  dungeon_layouts/                             NEW
    imperium_small.json        — 3–5 rooms
    imperium_medium.json       — 6–10 rooms
    imperium_large.json        — 11–20 rooms (*used by the showcase*)
    mine_small.json
    mine_medium.json
    cult_small.json
    cult_medium.json
    cave_small.json
    cave_medium.json
    overgrown_small.json
    overgrown_medium.json
    anchor_old_howl.json       — pinned 3-room layout for Old Howl
    anchor_imperium_showcase.json — pinned 8-room layout for the showcase
  loot_tables.json             EXTEND — add ~10 dungeon-tier tables: imperium_t1/t2/t3, mine_t1/t2, cult_t1/t2, cave_t1/t2, overgrown_t1
  npc_templates.json           EXTEND — add: imperium_feral_canid, imperium_feral_felid, imperium_undead_thrall, imperium_undead_overseer, mine_collapsed_brigand, cult_thorn_acolyte, cult_inheritor_initiate, cave_dire_wolf, cave_giant_centipede, overgrown_revenant, plus per-dungeon-type spawn-kind override map
  items.json                   EXTEND — add scent_mask_military, scent_mask_deep_cover (Phase 6.5 M5 carryover), and the new quest items: imperium_relic, parents_journal, parents_formula, maw_sigil. Mark these `kind: "quest_item"` (new ItemKind: non-droppable, no weight, dialogue-trigger only). Healing potion already exists; ConsumableHandler routes it.
  quests/
    side_act_i_old_howl.json   EXTEND — replace the narrative `give_item` step with `enter_anchor:poi:old_howl` then `combat_outcome` triggers + `give_item` on container loot
    main_act_i_003_following_dead.json   EXTEND — replace the narrative Lacroix kill/interrogate with a real `spawn_npc:lacroix at briarstead.workshop` step gated on `WorldClock.IsNight`, plus a `start_encounter` effect on the `lacroix.fight` dialogue node. Interrogate-then-betray branch emits `rep_event:Betrayal` which auto-cascades.
  dialogues/
    millhaven_lacroix.json     EXTEND — add the `start_encounter` effect on the "settle this here" branch; add post-combat dialogue branches for chase/interrogate/dead
```

### 4.2 The dungeon-as-scene-swap doctrine (the explicit Phase-6 exception)

Phase 6 §3.2 said:

> Buildings are tactical-tile stamps, not a separate scene… This avoids
> an "interior scene" subsystem until Phase 7 needs one for dungeons.

Phase 7 needs one. Here's the contract:

- A `Dungeon` is its own bounded tactical-tile array, sized by room
  count: roughly `roomCount × 12 tiles + roomCount × 8 corridor tiles`,
  rounded up to a power-of-two side. A small dungeon is ~64×64 tiles
  (1 chunk worth); a large dungeon is ~192×192 (≈ 9 chunks worth).
- When the player crosses a `Stairs` deco that maps to a PoI's
  entrance, `PlayScreen.EnterDungeon(poiId)`:
  1. Lazily generates the `Dungeon` (or restores from
     `DungeonStateSnapshot` if previously visited and modified).
  2. Saves the current player world-pixel position into
     `_savedWorldPosition`.
  3. Sets `_activeDungeon = dungeon`; `_activeMapView = _dungeonRenderer`.
  4. Repositions the player to the dungeon's entrance-tile centre.
  5. Pauses `ChunkStreamer` (no chunk eviction, no streaming).
- Movement, combat, dialogue, save/load work *exactly* the same inside
  the dungeon as outside. Same camera, same input, same `Encounter`
  resolver. The only difference is the tile source.
- Exit triggers when the player crosses an `ExitTile` (always the
  entrance tile by default; some templates declare a separate exit):
  1. Flushes any room/door/loot/kill state into `DungeonState`.
  2. Restores `_activeDungeon = null`; `_activeMapView = _tacticalRenderer`.
  3. Restores `player.Position = _savedWorldPosition` (one tile outside
     the entrance, so the player doesn't immediately re-enter).
  4. Resumes `ChunkStreamer`.

This is a **soft** scene swap: nothing about the camera, input,
encounter, or save model changes. Only the tile array the renderer
reads from changes. From the user's POV it's seamless — same `WASD`,
same fights, same TAB inventory, same J quest log.

### 4.3 Coordinate space

Inside a dungeon the coordinate space is **dungeon-local tactical
tiles**: `(0, 0)` to `(dungeon.W, dungeon.H)`. The player's world-tile
is *frozen* at the PoI's world-tile while inside (so `WorldClock`
travel-time-by-distance and rep-propagation still resolve sensibly,
since the player is "at" the PoI from the world's POV). The player's
*display* position is dungeon-local; serialisation captures both
contexts.

```
PlayerActor.Position    = dungeon-local world pixels (when in dungeon)
PlayerActor.WorldTile   = the PoI's world-tile (pinned)
PlayerActor.InDungeon   = poiId or null
```

### 4.4 The dice contract (extended)

Phase 5 introduced encounter-seeded RNG. Phase 6 extended it to
dialogue. Phase 6.5 added levelling + passing detection. Phase 7
adds dungeons:

```
dungeonLayoutSeed   = worldSeed ^ C.RNG_DUNGEON_LAYOUT ^ poiId
roomPickSeed        = dungeonLayoutSeed ^ C.RNG_ROOM_PICK ^ roomSlotIdx
populateSeed        = dungeonLayoutSeed ^ C.RNG_DUNGEON_POPULATE ^ roomId
lootContainerSeed   = dungeonLayoutSeed ^ C.RNG_DUNGEON_LOOT ^ containerSlotIdx
```

Same pattern as Phase 5/6: split per subsystem so two players visiting
the same PoI at the same `worldSeed` see the same layout, but their
*play* (which doors they open first, which monsters they kill) diverges
the inventory and combat state independently.

New constants (final hex values to be assigned at implementation time,
distinct from existing sub-streams):

```csharp
public const ulong RNG_DUNGEON_LAYOUT   = 0xD06E07AUL;
public const ulong RNG_ROOM_PICK        = 0x40072EUL;
public const ulong RNG_DUNGEON_POPULATE = 0x70757UL;
public const ulong RNG_DUNGEON_LOOT     = 0xD0717EUL;  // distinct from RNG_LOOT (encounter drops)
```

The existing `RNG_POI = 0x901F1UL` (worldgen-time PoI placement) and
`RNG_LOOT = 0x107EUL` (post-encounter drops) are unchanged; Phase
6.5's `RNG_LEVELUP = 0x1E7E107UL` and `RNG_PASSING = 0x9A55E5UL` are
unchanged.

---

## 5. Subsystem detail

### 5.1 Room templates

`RoomTemplateDef` JSON:

```jsonc
{
  "id": "imperium.coliseum_corridor_short",
  "type": "imperium",
  "built_by": "imperium",      // also: canid|felid|mustelid|ursid|cervid|bovid|leporid|none
  "size_class": "medium",      // small|medium|large; used by layout matcher
  "roles_eligible": ["transit", "narrative"],
  "footprint_w_tiles": 12,
  "footprint_h_tiles": 8,
  "grid": [
    "############",
    "#..........#",
    "#.D........#",
    "#.....@....#",
    "#.....C....#",
    "#..........#",
    "#..........#",
    "############"
  ],
  // legend: # wall, . dungeonfloor, , rubble, T trap-slot, C container-slot,
  //         @ encounter-slot, D door-slot, M mosaic-tile (narrative),
  //         P pillar, B brazier, S stairs (entry/exit only)
  "doors":          [{ "x":  2, "y": 2, "facing": "W" }],
  "encounter_slots":[{ "x":  6, "y": 3, "kind": "PoiGuard", "weight": 1.0 }],
  "container_slots":[{ "x":  6, "y": 4, "loot_table_band": "t2" }],
  "decos":          [{ "x":  9, "y": 2, "deco": "Pillar" }],
  "narrative_text": null
}
```

Grid characters map to `(TacticalSurface, TacticalDeco, TacticalFlags)`
triples in code, not data. Template authoring is editing 2D ASCII art
plus a couple of metadata blocks — designer-friendly.

`narrative_text` is the environmental-storytelling string surfaced by
`Scent-Broker / Scent Literacy` (the InteractionScreen scent-overlay
panel that Phase 6.5 M1 wired) and by the post-clear summary. Most
templates leave it null; "narrative" role templates (audience chamber,
collapse site, abandoned camp) provide a paragraph of prose. Phase
6.5's `master_nose` (Scent-Broker L11) reads up to 3 tags from any NPC
in the room *plus* the room's narrative_text — making narrative rooms
information-dense for Scent-Broker PCs.

### 5.2 Dungeon layout

`DungeonLayoutDef` JSON:

```jsonc
{
  "id": "imperium_medium",
  "dungeon_type": "ImperiumRuin",
  "size_band": "medium",                     // small|medium|large
  "room_count_min": 6,
  "room_count_max": 10,
  "branching": "branching",                  // linear|branching|loop
  "required_roles": ["entry", "narrative", "boss"],
  "optional_roles":  ["loot", "dead-end"],
  "loot_table_per_band": {
    "t1": "loot_dungeon_imperium_t1",
    "t2": "loot_dungeon_imperium_t2",
    "t3": "loot_dungeon_imperium_t3"
  },
  "spawn_kind_distribution": {
    "PoiGuard": 0.7,
    "WildAnimal": 0.2,                       // ferals
    "Brigand": 0.1                           // looters in the ruin
  },
  "level_band_to_loot_band": {               // PoI's LevelBand → which loot table band rolls
    "0": "t1", "1": "t1", "2": "t2", "3": "t3"
  }
}
```

`DungeonLayoutBuilder` algorithm (deterministic per `dungeonLayoutSeed`):

1. Roll `roomCount` uniformly in `[min, max]`.
2. Pick the entry-room template (filtered to `role: "entry"`).
3. For the next `roomCount - 1` slots, pick templates filtered by
   eligibility + `BuiltBy` consistency (Imperium dungeons mix Imperium
   and "none" templates; Mustelid Cult Dens mix Mustelid + "none"; etc.).
   Required roles (`narrative`, `boss`) must be assigned by the end —
   reserved slots are inserted last.
4. `RoomGraphAssembler` connects rooms:
   - **linear**: each room connects to the previous via the first
     compatible door pair.
   - **branching**: each room beyond the entry connects to one prior
     room (room `i` connects to a uniformly-random prior `j < i`); some
     rooms get two children, others zero. Rejects layouts where
     reachability fails (BFS from entry).
   - **loop**: branching, plus one extra connection that closes a loop.
5. Place rooms in dungeon-local tile space using a simple grid-pack
   algorithm: rooms snap to a 16-tile grid; corridors run between
   matched door pairs along Manhattan paths; the dungeon's bounding
   box is the union AABB.
6. Reject and retry the whole layout up to 8 times if any constraint
   fails (overlap, unreachability, missing required role). After 8
   rejects the generator falls back to a guaranteed-valid linear
   layout — logged loudly.

The 8-retry-then-linear-fallback ceiling is critical: dungeon
generation must never be unbounded. Caught by `DungeonGeneratorBudgetTests`
(M1).

### 5.3 The five dungeon types — Phase 7 content scope

| Type | Phase 7 templates | Phase 7 layouts | Distinctive features | Authored loot |
|---|---:|---:|---|---|
| **Imperium Ruin** (showcase) | ~30 | small/medium/large | Stone corridors, mosaics, sarcophagi, undead/feral occupants, Imperium artifacts | `imperium_t1..t3` (3 tables, ~15 items each) |
| **Abandoned Mine** | ~12 | small/medium | Tunnels, cave-ins, mineral veins, brigand or feral occupants | `mine_t1..t2` |
| **Cult Den** | ~10 | small/medium | Hideout aesthetic, scent-warded chambers, alchemical labs, Inheritor or Thorn Council acolytes | `cult_t1..t2` |
| **Natural Cave** | ~10 | small/medium | Wildlife dens, rough rock, occasional underground stream tile, dire-wolf / giant-centipede occupants | `cave_t1..t2` |
| **Overgrown Settlement** | ~10 | small/medium | Abandoned village layout, vegetation overgrowth, weathered building footprints, revenant or bandit occupants | `overgrown_t1` |

Imperium Ruin gets the deepest content investment because it's the
master-plan-mandated showcase. The other four types ship "minimum
viable": enough variety that any seed feels distinct, but not enough
to fully showcase the design. Phase 8/9 polish + content packs fill in
the rest.

### 5.4 Clade-responsive movement

`ClademorphicMovement.GetCostMultiplier(playerSize, room.BuiltBy) → float`:

| Player size | Built by Mustelid | Built by Ursid | Built by Cervid | Built by Bovid | Built by Imperium / None |
|---|:---:|:---:|:---:|:---:|:---:|
| Small      | 1.0 | 1.5 (exposed) | 1.0 | 1.2 | 1.0 |
| Medium     | 1.2 | 1.0           | 1.0 | 1.0 | 1.0 |
| Med-Large  | 1.5 | 1.0           | 1.0 | 1.0 | 1.0 |
| Large      | 2.0 (squeezing) | 1.0 | 1.2 (antler clearance) | 1.0 | 1.0 |

Cost multiplier applies to tactical-tile movement budget per turn —
a Large PC in a Mustelid tunnel takes twice as many "movement points"
to cross a tile, effectively halving their per-turn movement range.
Combat reach + LOS unchanged; this is *only* movement budget.

**Hybrid PCs** use their **dominant-lineage** clade for the size
lookup. A Wolf-Folk × Hare-Folk hybrid with `DominantParent: Sire`
reads as Wolf-Folk (Medium); with `DominantParent: Dam` reads as
Hare-Folk (Small). This matches the Phase 6.5 hybrid passing /
presenting-clade contract.

Implementation: a per-room cached multiplier read by
`TacticalMovementRules.LegalMovesFrom(actor, dungeonScene)` when the
scene is a `DungeonScene`. Outside dungeons the multiplier is always 1.0
(buildings don't have `BuiltBy` — they're tied to settlement clade
demographics, which is a Phase-6 concept that's already too soft to
gate movement on).

The same multiplier surfaces in dialogue prose for Scent-Broker /
narrative effects — no mechanical hook in Phase 7, but the data is
captured.

### 5.5 Loot

`LootGenerator.RollContainer(tableId, lootContainerSeed) → ItemInstance[]`:

```csharp
public static class LootGenerator {
    public static ItemInstance[] RollContainer(string tableId,
                                                ulong containerSeed,
                                                ContentResolver content) {
        var table = content.LootTable(tableId);
        var rng   = new SeededRng(containerSeed);
        var result = new List<ItemInstance>();
        foreach (var drop in table.Drops) {
            if (rng.NextDouble() > drop.Chance) continue;
            int qty = drop.QtyMin + (int)(rng.NextUInt64() % (uint)(drop.QtyMax - drop.QtyMin + 1));
            result.Add(new ItemInstance(content.Item(drop.ItemId), qty));
        }
        return result.ToArray();
    }
}
```

Per-table `Drops` follow the existing `LootTableDef` schema. New
dungeon-tier tables added to `loot_tables.json`:

```jsonc
{
  "id": "loot_dungeon_imperium_t2",
  "drops": [
    { "item_id": "fang",            "qty_min": 5,  "qty_max": 25, "chance": 1.0 },
    { "item_id": "rend_sword",      "qty_min": 1,  "qty_max": 1,  "chance": 0.10 },
    { "item_id": "chain_shirt",     "qty_min": 1,  "qty_max": 1,  "chance": 0.08 },
    { "item_id": "scent_mask_basic","qty_min": 1,  "qty_max": 2,  "chance": 0.20 },
    { "item_id": "scent_mask_military","qty_min": 1, "qty_max": 1, "chance": 0.06 },
    { "item_id": "imperium_relic",  "qty_min": 1,  "qty_max": 1,  "chance": 0.05 },
    /* ... */
  ]
}
```

`imperium_relic`, `howl_stone`, `parents_journal`, `parents_formula`,
`maw_sigil` are the authored quest-loot items needed for Acts I–II
content. They live in `items.json` with `kind: "quest_item"` (a
new `ItemKind` value Phase 7 adds — non-equippable, non-droppable, no
weight cost, dialogue-trigger-only).

### 5.6 The dialogue → combat handoff

New dialogue effect kind:

```jsonc
{
  "text": "I've heard enough. Settle this here.",
  "next": "<end>",
  "effects": [
    { "kind": "rep_event", "event": { "type": "DIALOGUE", "magnitude": -10 } },
    { "kind": "start_encounter", "npc_id": "$active", "advantage": "neither" }
  ]
}
```

`$active` is shorthand for "the NPC the player is currently talking to".
Alternatively, a quest-driven `start_encounter` can target a specific
named role (`role:millhaven.lacroix`) via `AnchorRegistry`.

`DialogueRunner` handling:

1. On `start_encounter` effect: capture the `(npcId, advantage)` payload.
2. Pop `InteractionScreen` from the screen stack.
3. Push `CombatHUDScreen` with a freshly-built `Encounter` from
   `Encounter.FromDialogueHandoff(worldSeed, npc, player, advantage)`.
4. The `EncounterId` is `(seed ^ RNG_COMBAT ^ "DLG" ^ npcId)` — stable
   across save/load, distinct from organic-LoS encounters with the
   same NPC.
5. NPC's `Allegiance` flips to `Hostile` for the duration of the
   encounter (and stays Hostile post-combat if alive).

### 5.7 Quest engine: `spawn_npc` / `despawn_npc` made real

Currently (Phase 6 deviation, still in code at
[QuestEngine.cs:294](Theriapolis.Core/Rules/Quests/QuestEngine.cs)):
both effects log to `engine.Journal` and do nothing in-world.

Phase 7 makes them resolve:

```jsonc
{
  "kind": "spawn_npc",
  "template_id": "lacroix_brigand_marauder",
  "anchor": "anchor:briarstead.workshop",     // or "world_tile:[137,82]" or "dungeon:poi_old_howl.room:boss"
  "named_role": "millhaven.lacroix",          // optional; if set, registers in AnchorRegistry
  "allegiance": "hostile"
}
```

Resolution order in `QuestEngine.RunEffect`:

1. **anchor** target: look up via `AnchorRegistry`. If anchor resolves to
   a Settlement, place the NPC at the settlement's centre tile (or at a
   building role anchor if `anchor:settlement.role` is given). If
   it resolves to a PoI, place at the PoI's world-tile *unless* the PC
   is inside the dungeon, in which case place in the dungeon's
   designated room (`anchor:poi.room:N`).
2. **world_tile** target: place at world-pixel center of the given tile.
3. **dungeon** target: if the player is inside the matching dungeon,
   place in the named room; if not, mark a deferred spawn that resolves
   on next dungeon entry.
4. Call `ActorManager.SpawnNpc(template, position, allegiance)`.
5. If `named_role` set, register the new actor in `AnchorRegistry`.

`despawn_npc` is symmetric: resolve target, find the matching NpcActor
(by id or named-role lookup), call `ActorManager.RemoveActor(id)`.

Both are deterministic per `(worldSeed, questId, stepId, effectIdx)`
when they need to roll (e.g. choosing one of three valid spawn
locations within an anchor).

### 5.8 Auto-fire of betrayal cascades from quest effects (Phase 6.5 M7 carryover)

Phase 6.5 shipped `BetrayalCascade.Apply` as caller-driven — the unit
tests submit explicit cascades; the dialogue/quest layer is expected to
opt in. Phase 7 wires the auto-fire path *only in the quest engine*:

```csharp
// QuestEngine.RunEffect for "rep_event" effect:
case "rep_event":
    rep.Submit(e.RepEvent, content.Factions);
    if (e.RepEvent.Kind == RepEventKind.Betrayal && ctx.Actors != null) {
        var betrayedNpc = ctx.Actors.FindByNamedRole(e.RepEvent.TargetRole)
                          ?? ctx.Actors.FindById(e.RepEvent.TargetId);
        if (betrayedNpc != null) {
            BetrayalCascade.Apply(e.RepEvent, rep, betrayedNpc, ctx.Actors.Npcs, content.Factions);
        }
    }
    break;
```

This keeps `PlayerReputation.Submit` semantically pure (per the M7
deviation rationale), keeps test code that submits synthetic events
unaffected, and gives authored quest content automatic cascades when
they emit Betrayal events. The Lacroix interrogate-then-betray branch
is the canonical exerciser.

### 5.9 Hybrid character creation UI (Phase 6.5 M4 carryover)

`CharacterCreationScreen` gets a "Hybrid origin (advanced)" checkbox at
the Clade step. On toggle, the single-clade picker is replaced with a
new `HybridParentPicker` Myra panel:

- Two side-by-side columns: **Sire** on the left, **Dam** on the right.
- Each column has a Clade dropdown and (filtered) Species dropdown.
- The Dam Clade dropdown excludes whatever the Sire Clade was set to
  (cross-clade enforcement).
- A center divider holds the **dominant-lineage** toggle (Sire / Dam)
  and a live trait-split summary (2-from-dominant + 1-from-secondary).
- The "Next" button is disabled until both columns are valid + dominant
  is selected.

The Phase 6.5 data path (`CharacterBuilder.IsHybridOrigin`,
`HybridSireClade`, etc.) is already shipped — the picker writes those
fields, and the existing `TryBuildHybrid(out err)` validator runs on
"Next".

### 5.10 The narrative dungeons: Old Howl, Lacroix break-in, Imperium Ruin showcase

#### Old Howl mine (Act I side quest, level 1 content)

- **Placement.** A new fixed-coordinate PoI placed by an *extension* of
  `PoIPlacementStage`: after general PoI placement, the stage looks for
  the nearest `PoiType.AbandonedMine` to Millhaven and tags it with
  `Anchor: OldHowlMine` (new enum entry). If no AbandonedMine exists
  within 30 tiles of Millhaven, one is force-placed (relaxing
  `POI_MIN_DIST_FROM_SETTLE` to 4 tiles for this anchor only).
- **Layout.** Forces the `mine_small` layout: 3 rooms (entry shaft,
  central gallery, deep tunnel). Hand-authored override file
  `Content/Data/dungeon_layouts/anchor_old_howl.json` pins the room
  selection so the experience is identical across seeds.
- **Spawns.** Three `brigand_footpad` NPCs: one in the entry shaft, two
  in the central gallery (pair). The deep tunnel is empty.
- **Loot.** A pre-authored container in the deep tunnel contains the
  `howl_stone` (quest item) plus `loot_mine_t1` rolls.
- **Quest hookup.** `side_act_i_old_howl.json` rewritten:
  - Replace `give_item: howl_stone` on quest entry → `enter_anchor:
    poi:old_howl` trigger.
  - Add an "all hostiles down" outcome trigger (existing
    `combat_outcome` trigger kind from Phase 6).
  - `give_item: howl_stone` happens when the player loots the deep-
    tunnel container.
  - Returns to Asha for the dialogue resolution unchanged.

#### Lacroix break-in (Act I climax, level 2–3 content)

- **Placement.** Lacroix is *spawned*, not placed. The
  `main_act_i_003_following_dead` quest's "ambush" step has a trigger
  `time_elapsed: 12 hours` AND `WorldClock.IsNight: true`. On fire,
  the step's `onEnter` runs:
  ```jsonc
  [
    { "kind": "spawn_npc",
      "template_id": "lacroix_brigand_marauder",
      "anchor": "anchor:briarstead.workshop",
      "named_role": "millhaven.lacroix",
      "allegiance": "hostile" }
  ]
  ```
- **Where the encounter happens.** Briarstead's workshop is a
  Settlement-tier building footprint already stamped by Phase 6's
  `SettlementStamper`. Lacroix spawns at that building's role-anchor
  tile.
- **Three branches preserved.**
  - **Kill** (combat, Lacroix dies): existing `lacroix_killed` flag set
    by `combat_outcome` trigger; existing dialogue tree rewards
    unchanged.
  - **Chase** (combat, Lacroix flees at <25% HP — uses the existing
    `WildAnimal` flee behaviour): new `lacroix_fled` flag + dialogue
    tree gets a new branch.
  - **Interrogate** (PRE-COMBAT — player presses E to open dialogue
    instead of attacking; existing `Allegiance.Neutral`-while-talking
    stays in effect; dialogue tree's "interrogate" branch fires):
    existing `lacroix_interrogated` flag, no combat ever happens.
    The branch can end with a `rep_event:Betrayal` if the player
    betrays Lacroix's information to the city watch — this auto-fires
    the cascade per §5.8.
- **`BuildingDelta`.** Lacroix's break-in mechanically broke the
  workshop's main door. A `BuildingDelta { door_broken: true }` is
  emitted on combat-start so the door state persists post-encounter
  even if the player saves and reloads. This is the first concrete
  use of the v8 `Buildings` save tag.
- **Hybrid PCs and Lacroix.** Lacroix is canonically a Wolf-Coyote
  hybrid in the worldbuilding — but this is *not* mechanically
  surfaced in Phase 7 except for Scent-Broker PCs reading
  `MawAffiliated` from his ScentTags (the Phase 6.5 M6 demo).
  Phase 7 does *not* gate any combat behaviour on his hybrid status.

#### Imperium Ruin showcase

- **Placement.** Identified at worldgen by tagging the closest
  `PoiType.ImperiumRuin` to Millhaven within Act-I travel range
  (40–80 tiles) as `Anchor: ImperiumRuinShowcase`.
- **Layout.** Forces a hand-authored override (`anchor_imperium_showcase.json`):
  1. **Entry hall** — broken pillars, a `narrative_text` entry that
     describes the gladiator-pit-history setup.
  2. **Coliseum corridor** — first encounter: 2 imperium_feral_canids.
  3. **Pillar room** — pillars give cover; no encounter; a container.
  4. **Mosaic atrium** — narrative room; the central mosaic depicts
     the gladiator pit's purpose; a Scent-Broker passive can read
     additional prose from the floor's residual scent.
  5. **Sarcophagus chamber** — 2 imperium_undead_thralls and a
     locked sarcophagus (DEX or STR check) with `imperium_t2` loot.
  6. **Dead-end tunnel** — single feral; container with `imperium_t1`.
  7. **Audience chamber** (narrative) — a body posed in the throne
     with a journal describing how the place fell.
  8. **Boss throne room** — 1 imperium_undead_overseer (level 3 elite)
     + 2 imperium_feral_canids; chest with `imperium_t3` + 1 guaranteed
     `imperium_relic` (quest item — surfaces in Act III dialogue).
- **Why it's the showcase.** Eight rooms is enough to feel like a
  proper delve without wearing out the player; the narrative beats
  (gladiator-pit history, the throne, the journal) carry environmental
  storytelling per `procgen.md` Layer 5; the boss room demonstrates
  the full encounter pipeline; the relic survives in inventory and
  shows up in Act III to prove cross-act state persistence.
- **Levelling expectation.** With Phase 6.5 levelling live, the
  showcase is tunable to "level 2-3 PC expected" via the boss's stat
  block (`imperium_undead_overseer` ≈ a level-3 brigand_marauder).
  A level-1 PC who walks in directly will struggle and is expected to
  retreat or grind Old Howl + side encounters first. The
  `--level N` Tools flag (Phase 6.5 M0 carryover, shipped in Phase 7
  M0) lets balance testers exercise level-1, level-2, and level-3
  walkthroughs in CI.

### 5.11 Save schema (v7 → v8)

Phase 6.5 bumped `SAVE_SCHEMA_VERSION` to 7. Phase 7 bumps to **8**.

```csharp
// v7 → v8 changes:
public AnchorRegistrySnapshot       Anchors    { get; set; } = new();   // NOW emitted (was reserved at TAG_ANCHORS=113)
public List<BuildingDelta>          Buildings  { get; set; } = new();   // NOW emitted (was reserved at TAG_BUILDINGS=114)
public List<DungeonStateSnapshot>   Dungeons   { get; set; } = new();   // NEW — TAG_DUNGEONS=115
```

`DungeonStateSnapshot` — only present if the dungeon has been *modified*
relative to its deterministic baseline:

```csharp
[MessagePackObject]
public sealed class DungeonStateSnapshot
{
    [Key(0)] public int      PoiId;
    [Key(1)] public int[]    ClearedRoomIds;
    [Key(2)] public int[]    OpenedDoorIds;
    [Key(3)] public int[]    LootedContainerIds;
    [Key(4)] public int[]    KilledNpcLocalIds;        // dungeon-local NPC ids
    [Key(5)] public bool     PartiallyExplored;        // helps the renderer draw fog-of-war when Phase 8 lands
}
```

Round-trip: on load, `DungeonRegistry` maps `PoiId → DungeonStateSnapshot`.
On first dungeon entry post-load, `DungeonGenerator` runs deterministically
against the baseline; then the snapshot is applied as overrides (cleared
rooms have empty NPC lists, looted containers are empty, opened doors
are flagged, killed NPCs are excluded).

`SaveCodec` tags:

```
TAG_ANCHORS      = 113   // promoted from reserved (Phase 7)
TAG_BUILDINGS    = 114   // promoted from reserved (Phase 7)
TAG_DUNGEONS     = 115   // NEW (Phase 7)
```

(Phase 6.5's character-section additions used `TAG_CHARACTER=100`'s
EOS-checked-append pattern, so they don't conflict with these new
tags.)

`V7ToV8` migration is **additive**: empty defaults for `Anchors` /
`Buildings` / `Dungeons`. Phase 6.5 saves load fine.

### 5.12 The consumable-item handler (Phase 6.5 M4 / M5 carryover)

A single dispatch point for "the player consumed item X":

```csharp
public static class ConsumableHandler
{
    public static ConsumeResult Consume(Item item, Character pc, ItemContext ctx)
    {
        switch (item.ConsumableKind)
        {
            case ConsumableKind.HealingPotion:
                int healed = item.HealAmount;
                if (pc.IsHybrid) {
                    healed = Math.Max(1, (int)(healed * HybridDetriments.HealingScale)); // 0.75
                }
                pc.Heal(healed);
                return ConsumeResult.Healed(healed);

            case ConsumableKind.ScentMask:
                var tier = ParseScentMaskTier(item.Id); // basic / military / deep_cover
                pc.Hybrid?.SetActiveMaskTier(tier);
                return ConsumeResult.MaskApplied(tier);

            // future: stim, antidote, food, etc.
            default:
                return ConsumeResult.Unrecognized(item.Id);
        }
    }
}
```

`InventoryScreen`'s "Use" button routes here. The hybrid 0.75×
multiplier on healing potions is the M4 deviation extension; the
mask-tier dispatch is the M5 deviation extension. Both land in the
same code path so reviews / tests / UI scaling stay coherent.

The `scent_mask_basic` item already exists. Phase 7 M2 adds:

```jsonc
{
  "id": "scent_mask_military",
  "kind": "consumable",
  "consumable_kind": "scent_mask",
  "weight": 0.5
},
{
  "id": "scent_mask_deep_cover",
  "kind": "consumable",
  "consumable_kind": "scent_mask",
  "weight": 0.5,
  "rarity": "uncommon"
}
```

These appear in dungeon loot tables (`imperium_t2` carries military
masks at low chance; `imperium_t3` carries one deep-cover mask).

### 5.13 The InteractionScreen first-meet hooks (Phase 6.5 M4 / M5 carryovers)

`InteractionScreen.OnOpen` extends to wire two Phase-6.5 deferrals:

```csharp
public void OnOpen(NpcActor npc) {
    // ... existing scent-overlay panel render ...

    // Phase 6.5 M5 carryover: passing-detection on first meet
    if (pc.IsHybrid && !pc.Hybrid.NpcsWhoKnow.Contains(npc.Id)) {
        var passingResult = PassingCheck.RollAndApply(pc, npc, npc.MemoryFlags,
            ctx.SeedFor(npc.Id, encounterIdx));
        if (passingResult.Detected) {
            // RollAndApply already appended the RepEventKind.HybridDetected entry
            // and added npc.Id to pc.Hybrid.NpcsWhoKnow.
            ShowDetectionToast(npc);
        }
    }

    // Phase 6.5 M4 carryover: first-CHA-stranger pip + nonverbal disadvantage
    if (pc.IsHybrid && pc.Hybrid.NpcsWhoKnow.Contains(npc.Id)) {
        if (npc.SettlementProgressivity == Progressivity.NonProgressive
            && !pc.PerNpcChaPipsConsumed.Contains(npc.Id)) {
            ShowSocialStigmaPip();   // -2 to first CHA roll
        }
        if (npc.IsPurebred) {
            FlagDialogueRollsTagged("nonverbal_cha", DisadvantageMod.True);
        }
    }
}
```

This is straightforward: the data paths are all in place from Phase
6.5; Phase 7 just calls them at the open-dialogue moment.

---

## 6. Determinism & RNG

| RNG sub-stream | Used by |
|---|---|
| `RNG_DUNGEON_LAYOUT` | Per-PoI room-graph generation (room count, branching policy, special-room placement) |
| `RNG_ROOM_PICK`      | Within a layout, picking which template fills each role-eligible slot |
| `RNG_DUNGEON_POPULATE` | Per-room spawn selection (which NPC template fills each encounter slot) |
| `RNG_DUNGEON_LOOT`   | Per-container loot rolls (separate from `RNG_LOOT` which is encounter drops) |

Per-dungeon sub-seed:
`dungeonLayoutSeed = worldSeed ^ RNG_DUNGEON_LAYOUT ^ poiId`.

The dungeon's runtime state advances deterministically through
`Encounter`-mediated combat (which still uses `RNG_COMBAT` per Phase 5).
A combat that begins inside a dungeon runs through the *same* encounter
machinery; its `EncounterId` is `(seed ^ RNG_COMBAT ^ poiId ^ roomId
^ encounterIdxInRoom)`, distinct from world-chunk encounters.

Phase 6.5's `RNG_LEVELUP` and `RNG_PASSING` continue to operate
as-shipped. Phase 7 does not introduce new sub-streams for the
6.5-carryover work — the auto-fire BetrayalCascade reuses the
existing `RNG_BETRAYAL` semantics already inside `BetrayalCascade.Apply`,
the PassingCheck wire-in just calls existing `RollAndApply`, and the
ConsumableHandler is deterministic without RNG (item → effect is a
pure dispatch).

**Tests required:**

- `DungeonGeneratorDeterminismTests` — same `(seed, poiId)` → byte-
  identical room ids, AABBs, spawn lists, container ids, across 5
  process runs.
- `DungeonStateSaveRoundTripTests` — modify a dungeon (clear two
  rooms, loot one container), save, load, assert the snapshot
  applied + remaining rooms still generate identical to baseline.
- `LootDeterminismTests` — same `(table, containerSeed)` → identical
  item list across runs.
- `OldHowlIntegrationTests` — full Old Howl walkthrough at fixed
  seed reaches the Howl-stone with the expected 3 brigand kills.
- `LacroixIntegrationTests` — three branches (kill / flee /
  interrogate) all set the expected flags + faction standings at
  fixed seed; the interrogate-then-betray sub-branch correctly
  triggers the Phase 6.5 betrayal cascade.
- `QuestBetrayalAutoFireTests` — quest-engine emits Betrayal event,
  cascade auto-fires; `PlayerReputation.Submit` direct call does *not*
  auto-fire (preserves the M7 deviation purity).
- `PassingCheckFirstMeetTests` — opening InteractionScreen on first
  meet triggers RollAndApply once and only once.
- `ConsumableHandlerTests` — healing potion + hybrid PC = 0.75×
  scaled heal; scent_mask_military sets ActiveMaskTier=Military.

---

## 7. Constants going into `Constants.cs`

```csharp
// ── Phase 7: RNG sub-streams ─────────────────────────────────────────
public const ulong RNG_DUNGEON_LAYOUT   = 0xD06E07AUL;
public const ulong RNG_ROOM_PICK        = 0x40072EUL;
public const ulong RNG_DUNGEON_POPULATE = 0x70757UL;
public const ulong RNG_DUNGEON_LOOT     = 0xD0717EUL;

// ── Phase 7: Dungeon generation ─────────────────────────────────────
public const int   DUNGEON_SMALL_ROOMS_MIN  = 3;
public const int   DUNGEON_SMALL_ROOMS_MAX  = 5;
public const int   DUNGEON_MED_ROOMS_MIN    = 6;
public const int   DUNGEON_MED_ROOMS_MAX    = 10;
public const int   DUNGEON_LARGE_ROOMS_MIN  = 11;
public const int   DUNGEON_LARGE_ROOMS_MAX  = 20;
public const int   DUNGEON_LAYOUT_MAX_ATTEMPTS = 8;   // before falling back to linear

public const int   ROOM_GRID_SNAP_TILES     = 16;     // rooms snap on a 16-tile grid
public const int   ROOM_CORRIDOR_MIN_W      = 2;      // corridor min width in tiles
public const int   ROOM_CORRIDOR_MAX_W      = 3;
public const int   ROOM_INTER_ROOM_GAP_TILES = 2;     // min space between adjacent rooms

// ── Phase 7: Dungeon scene ──────────────────────────────────────────
public const int   DUNGEON_AABB_PADDING     = 8;      // tactical-tile padding around the room AABB union

// ── Phase 7: Loot ───────────────────────────────────────────────────
public const float LOOT_TABLE_BAND_T1_THRESHOLD = 0.0f;  // level band 0-1 → t1
public const float LOOT_TABLE_BAND_T2_THRESHOLD = 2.0f;  // level band 2 → t2
public const float LOOT_TABLE_BAND_T3_THRESHOLD = 3.0f;  // level band 3 → t3

// ── Phase 7: Clade-responsive movement ──────────────────────────────
public const float MOVE_COST_MISMATCH_LIGHT  = 1.2f;   // soft mismatch
public const float MOVE_COST_MISMATCH_MED    = 1.5f;   // medium mismatch
public const float MOVE_COST_MISMATCH_HEAVY  = 2.0f;   // squeezing

// ── Phase 7: Locked door / trap ─────────────────────────────────────
public const int   LOCK_DC_TRIVIAL  = 10;
public const int   LOCK_DC_EASY     =  12;
public const int   LOCK_DC_MEDIUM   =  15;
public const int   LOCK_DC_HARD     =  18;

public const int   TRAP_DC_TRIVIAL  =  10;
public const int   TRAP_DC_EASY     =  12;
public const int   TRAP_DC_MEDIUM   =  15;

public const int   TRAP_DAMAGE_DICE_TRIPWIRE = 1;       // 1d6 piercing
public const int   TRAP_DAMAGE_DIE_TRIPWIRE  = 6;

// ── Phase 7: Dungeon clear bonus ────────────────────────────────────
public const float DUNGEON_CLEAR_XP_BONUS_FRACTION = 1.0f;  // bonus = highest-NPC-XP × this; tunable

// ── Phase 7: Save ───────────────────────────────────────────────────
// SAVE_SCHEMA_VERSION bumped to 8 (was 7 in Phase 6.5)
```

(Final hex values for the four RNG sub-streams to be verified
non-colliding with all existing sub-streams at implementation time —
the listed values are placeholders following the existing naming
pattern.)

---

## 8. Milestones

Each is a ship point: a branch with a self-contained set of changes,
green tests, and a feature you can demonstrate. Milestones are
**ordered for shippable progress**: every milestone leaves the game in
a playable, save-load-clean state, and each milestone is roughly the
same size.

**M0 — Phase 6.5 carryover + content schema.**
- `RoomTemplateDef` + `DungeonLayoutDef` records.
- `ContentLoader.LoadRoomTemplates` (recursive scan), `LoadDungeonLayouts`.
- Author **5 Imperium room templates** + **3 mine templates** + **2 cave
  templates** as a vertical-slice content set. Author **2 dungeon
  layouts** (`imperium_medium`, `mine_small`).
- `ContentValidate` extended with the room-grid + reference checks.
- `dungeon-render` Tools command stub: loads templates, renders one
  template to PNG.
- **Phase 6.5 M0 carryover:** `--level N` flag on `character-roll`.
  Headless level-N character generation works for all 8 classes ×
  levels 1–20.
- **Phase 6.5 M4 carryover:** `HybridParentPicker` Myra wizard step
  in `CharacterCreationScreen`. Side-by-side Sire/Dam picker;
  cross-clade enforcement; dominant-lineage toggle. Existing
  `CharacterBuilder.TryBuildHybrid` validator wires through unchanged.
- **Phase 6.5 M2 carryover (start):** wire 4 of the 12 remaining L3
  subclass features (one per class that doesn't have one yet —
  pick the most combat-relevant feature per class).
- **Ship point:** `dotnet run -- content-validate` exits 0 with all
  Phase-7 content recognized. `dotnet run -- dungeon-render --template
  imperium.entry_grand_hall --out hall.png` produces a PNG of the
  template's tile grid. `dotnet run -- character-roll --class fangsworn
  --level 5` produces a level-5 Fangsworn with all subclass features
  loaded. The hybrid-creation wizard works in-game; a hybrid PC can
  be created and the character sheet shows the dual-clade icon.
  640 + ~25 tests green.

**M1 — Dungeon generator + scene-swap plumbing + remaining L3 + L7 features.**
- `Dungeon`, `Room`, `RoomConnection`, `DungeonGenerator`,
  `DungeonLayoutBuilder`, `RoomGraphAssembler`, `RoomTilePainter`.
- New `TacticalSurface` / `TacticalDeco` / `TacticalFlags` entries.
- `DungeonScene` + `DungeonRenderer` (IMapView).
- `PlayScreen.EnterDungeon(poiId)` / `ExitDungeon()` plumbing.
  `Stairs` deco interaction triggers entry; entrance-tile re-cross
  triggers exit.
- `TacticalChunkGen.Pass6_PoiEntrance` stamps a `Stairs` deco at every
  PoI's world-pixel center on the surface chunk that contains it.
- `dungeon-render --seed N --poi <id>` runs the full pipeline and dumps
  a PNG of the assembled dungeon.
- All chunk-determinism tests still green; new
  `DungeonGeneratorDeterminismTests` + `DungeonReachabilityTests`
  + `DungeonScaleTests` + `DungeonSceneSwapTests` + `DungeonGeneratorBudgetTests`.
- **Phase 6.5 M2 carryover (continued):** wire the remaining 8 L3
  subclass features (12 total wired by end of M1, all 16 covered).
  Wire the ~5 combat-touching L7 subclass features that the showcase
  content exercises.
- **Ship point:** Walk to any PoI tile in-game → press E on the stairs
  → screen swaps to a dungeon view with rooms + corridors. Walk back
  onto the entrance tile → return to surface. No combat or loot yet
  (dungeons are empty). All L3 subclass features wired and exercised
  by tests.

**M2 — Spawns + loot + clade-responsive movement + consumable handler.**
- `NpcInstantiator.SpawnInDungeon(dungeon, populateSeed)` walks each
  room's encounter slots; consults `npc_templates.json`'s
  `spawn_kind_to_template_by_dungeon_type` table; spawns NPCs at slot
  positions with `Allegiance: Hostile`.
- `LootGenerator.RollContainer` wired to `Container` decos; container
  decos register themselves in the dungeon's loot list at generate
  time; first interaction (E key) opens a buy-style modal showing the
  rolled `ItemInstance[]`; player transfers items to inventory.
- New dungeon-tier loot tables in `loot_tables.json`.
- New dungeon-themed NPC templates in `npc_templates.json` (~10 new
  templates).
- `ClademorphicMovement` static helper + `TacticalMovementRules`
  hook-up; hybrid PCs use dominant-lineage size for the lookup.
- **Phase 6.5 M5 carryover:** `ConsumableHandler.Consume` central
  dispatch. `InventoryScreen` "Use" button routes here. Wires
  scent_mask_basic / military / deep_cover (latter two added to
  `items.json`) and healing potions.
- **Phase 6.5 M4 carryover:** healing-potion path applies the 0.75×
  Hybrid Medical Incompatibility scaling.
- `DungeonClademorphicTests`, `LootDeterminismTests`,
  `DungeonEncounterDeterminismTests`, `ConsumableHandlerTests`,
  `HealingPotionMedicalIncompatibilityTests`.
- **Ship point:** Walk into a generated mine PoI → fight 2 brigands →
  loot a chest → pick up a scent_mask_military → exit. A Wolf-Folk
  Fangsworn moves at normal speed; a Wolverine-Folk PC in the same
  mine moves at half speed (Mustelid template + Large-ish player →
  mismatch). Save and reload mid-dungeon → state persists. A hybrid
  PC consuming a healing potion heals 75% of the listed amount; a
  purebred PC heals 100%. A hybrid PC consuming a deep-cover mask
  has `Hybrid.ActiveMaskTier == DeepCover`.

**M3 — Imperium Ruin showcase content + full Imperium template set.**
- Full ~30-template Imperium content drop authored.
- The `anchor_imperium_showcase` layout pinned to a specific PoI near
  Millhaven by an extension to `PoIPlacementStage`.
- 8 rooms hand-tuned: entry, corridor, pillar room, mosaic atrium,
  sarcophagus chamber, dead-end, audience chamber, boss throne.
- 3 narrative rooms with `narrative_text` prose.
- 1 boss NPC: `imperium_undead_overseer` (level 3 elite stat block).
- 1 quest item: `imperium_relic` (drops in the boss chest, surfaces
  in Act III dialogue).
- **Ship point:** Walk to the showcase PoI → enter → clear 8 rooms →
  defeat the overseer → loot the relic → exit. The full delve takes
  20–30 in-game minutes; the player has the relic in inventory; the
  ruin's `DungeonStateSnapshot` records all 8 rooms cleared.
  `--level N` flag from M0 used to verify level-1 / level-2 / level-3
  PCs all face appropriate difficulty (level-1 fails the boss; level-3
  clears it cleanly).

**M4 — Quest engine: spawn_npc / despawn_npc + dialogue→combat handoff + 6.5 dialogue carryovers.**
- `QuestEngine.RunEffect` resolves real `spawn_npc` / `despawn_npc`
  with anchor / world_tile / dungeon target kinds.
- **Phase 6.5 M7 carryover:** `QuestEngine.RunEffect` auto-fires
  `BetrayalCascade.Apply` on `rep_event:Betrayal` effects.
- `DialogueRunner` handles the new `start_encounter` effect kind.
- `Encounter.FromDialogueHandoff` factory + stable EncounterId from
  `(seed, npcId)`.
- **Phase 6.5 M5 carryover:** `InteractionScreen.OnOpen` wires
  `PassingCheck.RollAndApply` on first-meet for hybrid PCs.
- **Phase 6.5 M4 carryover:** `InteractionScreen.OnOpen` surfaces the
  Illegible Body Language disadvantage flag and the Social Stigma
  -2 first-CHA pip on first interaction with non-progressive-settlement
  purebred NPCs.
- `QuestSpawnNpcTests`, `QuestDespawnNpcTests`,
  `DialogueToCombatHandoffTests`, `QuestBetrayalAutoFireTests`,
  `PassingCheckFirstMeetTests`, `HybridSocialStigmaTests`.
- **Ship point:** Author a tiny test quest that spawns a brigand at
  Millhaven's plaza on press of a debug key. Brigand appears, walks
  to the player, encounter triggers normally. Trigger a dialogue
  with the brigand and pick "settle this here" → combat starts
  cleanly. Save mid-handoff → load → identical state. A hybrid PC
  walking up to a Cervid villager triggers a passing-detection roll
  on first meet; a hybrid PC in a non-progressive settlement sees a
  -2 First-CHA pip on a stranger NPC.

**M5 — Old Howl mine + Lacroix climax wired up + BuildingDelta + recently-killed scent.**
- Old Howl mine PoI placement override + `anchor_old_howl` layout +
  `side_act_i_old_howl.json` quest rewritten to use `enter_anchor:
  poi:old_howl` + `combat_outcome` + `give_item: howl_stone`.
- `main_act_i_003_following_dead.json` rewritten: ambush step uses
  `time_elapsed` + `WorldClock.IsNight` trigger + `spawn_npc` effect
  for Lacroix.
- `millhaven_lacroix.json` dialogue tree extended with
  `start_encounter` on the "settle this here" branch + new
  post-combat dialogue branches for chase/interrogate/dead. The
  interrogate-then-betray sub-branch emits `rep_event:Betrayal`
  which auto-cascades via M4's wiring.
- `BuildingDelta` save schema; emitted on combat-start at Briarstead
  workshop (door broken).
- **Phase 6.5 M6 carryover:** `Resolver.AttemptAttack` on melee kill
  sets `HasRecentlyKilled` on the killer's scent profile. Dungeon
  combat exercises this — kill in one room, walk to another, the
  next NPC's first scent-read on the player carries
  `RecentlyKilled`.
- `OldHowlIntegrationTests`, `LacroixIntegrationTests`,
  `BuildingDeltaSaveRoundTripTests`, `RecentlyKilledScentTagTests`.
- **Ship point:** Replay Act I from M0 of Phase 6 with Phase-7
  content: Talk to Asha → walk to the Old Howl mine → real combat →
  loot the Howl-stone → return → dialogue resolves. Wait until
  night-time at Briarstead → Lacroix appears in the workshop →
  combat → all three branches resolvable → faction standings + quest
  flags identical to Phase-6 narrative-resolution end-state. The
  interrogate-then-betray branch correctly triggers betrayal cascade
  to Maw faction.

**M6 — Polish + remaining four dungeon types (Mine / Cult / Cave / Overgrown) content.**
- Cult Den, Natural Cave, Overgrown Settlement, and the rest of
  Abandoned Mine content authored (~10 templates each, ~2 layouts
  each).
- `loot_tables.json` rounded out for all five types.
- `npc_templates.json` rounded out (cave fauna, cult acolytes,
  overgrown revenants).
- One trap kind: `Trap` deco with DEX-save-DC-12 disarm; 1d6 piercing
  on fail. Used sparingly (1–2 per medium dungeon).
- One locked door kind: `DungeonDoor` deco with `LockDC` field; STR or
  DEX check on E-press; lockpick item consumes one charge if used.
- `loot-distribution` Tools command for designer-side balance review.
- `DungeonClearScreen` modal: shown on full clear; surfaces XP bonus
  + loot summary.
- **Ship point:** A complete Phase 7. Generate a fresh seed; visit any
  10 PoIs of mixed types; each feels distinct in tile aesthetic,
  enemy roster, and loot. Test count target: ~720+ green (640 from
  Phase 6.5 + ~80 new).

---

## 9. Risks & mitigations

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Authoring volume balloons (~70 room templates + 10 layouts + 10 loot tables + 10 NPC templates + 2 narrative dungeons + content updates to 4 quests + ~6 dialogue extensions + 12 L3 subclass features + 5 L7 features) | High | High | Front-load the schemas (M0 lands the validators before anyone authors content); split content authoring across milestones; defer 4-of-5 dungeon types' deep content to M6 (only Imperium ships full at M3). `content-validate` CI gate prevents broken content from blocking engine work. The 12 L3 features each follow established patterns from Phase 6.5's 4 wired subclasses — switch case + 4–6 unit tests per. |
| Phase 6.5 carryover work expands inside M0 | High | Med | M0 picks up `--level N`, HybridParentPicker, and 4 of 12 L3 features. The remaining 8 L3 features + 5 L7 features land in M1 alongside the dungeon-generator engine work. If M0 is running long, the L3 wirings can be deferred to dedicated pass after M1 — they're independent of the dungeon stack. |
| Procedural dungeon layouts produce visually broken results (rooms overlap, doors don't connect, unreachable rooms) | Med | High | M1 ships the 8-retry-then-linear-fallback ceiling. `DungeonReachabilityTests` runs on 100 random `(seed, poiId)` pairs and asserts every room reachable from entry; CI gate. `dungeon-render` Tools command renders any seed for visual QA before merging content. |
| Scene-swap feels janky (camera jumps, player position stutters, save/load loses dungeon state) | Med | High | M1's `DungeonSceneSwapTests` gates this. `_savedWorldPosition` restoration on exit is a 1-line change; the tricky part is mid-dungeon save/load — covered by `DungeonStateSaveRoundTripTests` from M2. Manual playtest at M3 ship-point. |
| Mid-dungeon mid-combat save/load determinism breaks | Med | High | Same shape as Phase 5/6/6.5 mid-combat save: `EncounterId` is stable per `(seed, poiId, roomId, encounterIdx)`; per-encounter `SeededRng` advances monotonically; resume re-creates the RNG and skips to the saved sequence. Tested by `DungeonEncounterDeterminismTests` + `MidCombatSaveRoundTripTests` extended with a dungeon scenario. |
| Phase 6 `spawn_npc` was a stub; making it real breaks Phase 6 quest integration tests | Low | Med | Phase 6's `ActIIntegrationTest` was scripted around the *narrative* resolution. M5 rewrites the test alongside the quest content so it asserts the *combat* resolution. The M5 ship-point demo *is* this verification. |
| `BuildingDelta` save tag introduces a new mutation point that future agents don't discover | Med | Med | The deviation table at end of plan + content-validate's reference checks ensure the tag is exercised. The tests gate it from regression. |
| Imperium Ruin showcase too hard at level 1 even with 6.5 levelling shipped | Med | Med | The showcase's level-band constants tune to "level 2–3 expected"; the boss's stat block is `imperium_undead_overseer` ≈ a level-3 brigand_marauder. `--level N` flag exercises level-1 / level-2 / level-3 in CI. A level-1 PC is *expected* to fail the boss and either flee, save-scum, or grind Old Howl + side encounters first — documented in the showcase's narrative-text. |
| Clade-responsive movement feels punishing or invisible | Med | Med | UI surfaces the multiplier as a small icon over the player sprite when active ("squeezing" / "exposed"). M2's manual test compares two PCs (Wolf-Folk vs Bear-Folk) in the same Mustelid mine and confirms the Bear-Folk feels noticeably slower. Hybrid PCs use the dominant-lineage size — testable by toggling `DominantParent` in the wizard. Tunable via `MOVE_COST_MISMATCH_*` constants. |
| Dungeon generation budget exceeds frame time on first entry | Low | Med | M1's `DungeonGenerator.Run` is benchmarked: a medium dungeon (8 rooms) generates in <50ms cold (no I/O — all templates pre-loaded). The 8-retry fallback caps total time at <400ms even in the worst case. `DungeonGeneratorBudgetTests` enforces. |
| Hybrid-mod-blending feels "too generous" once Imperium showcase exposes hybrid combat balance | Med | Low | M3 ship-point includes a `--hybrid` walkthrough at level 3. If the auto-accumulation deviation feels off, M6 polish can ship the player-choice picker per the Phase 6.5 M4 plan. Cost is 1 UI step + a content authoring pass. Recorded as open decision §10.10. |
| Auto-fire BetrayalCascade introduces side-effects in tests that previously passed | Low | Med | The auto-fire sits at the **`QuestEngine.RunEffect` layer**, not at `PlayerReputation.Submit`. Tests submitting synthetic `RepEvent`s directly via `Submit` are unaffected. The wiring is single-call-site and easy to trace. `QuestBetrayalAutoFireTests` explicitly verifies both paths. |
| `SAVE_SCHEMA_VERSION=8` migration drops Phase 6.5 saves | Low | High | Same shape as v6→v7: additive only. `V7ToV8MigrationTests` round-trip a Phase-6.5 save → asserts Phase 7 fields all empty. Migrations chain v5→v6→v7→v8; old saves walk the chain. |
| Lacroix encounter at Briarstead breaks because the workshop building isn't always stamped (settlement-stamp coverage gap) | Med | High | `LacroixIntegrationTests` runs at 3 different seeds and asserts the Briarstead workshop's role-anchor exists. If the test fails on any seed, Phase 6's Briarstead preset (which is already hand-authored — not procedural) is the source of truth and gets a direct edit. |
| Future agents conflate "narrative dungeon" with "procedural dungeon" architecture | Low | Med | The plan's §5.10 is explicit that anchor-overrides force a hand-authored layout file; the procedural pipeline runs *only* if `Settlement.Anchor == None`. Documented in code comments at `DungeonGenerator.Run` and `PoIPlacementStage.AssignAnchorPoIs`. |

---

## 10. Open decisions to resolve before M2

1. **Dungeon "facing" on first entry.** When the player enters, the
   camera is centred on the entrance tile. Should the camera snap
   immediately or pan smoothly? Proposed: snap (matches Phase 4's
   tactical scene-swap behaviour). Decision needed by M1.
2. **Walking onto an entrance tile vs. pressing E.** Proposed: `E` to
   confirm (matches the F-to-talk Phase-6 convention and prevents
   accidental dungeon entry mid-travel). Decision needed by M1.
3. **Dungeon completion XP award.** Per-NPC kill XP is already
   awarded; should clearing a dungeon (all rooms cleared + boss
   dead) grant an additional clear bonus? Proposed: yes, equal to
   the *largest single NPC's XP* in the dungeon (constant
   `DUNGEON_CLEAR_XP_BONUS_FRACTION = 1.0f` is the multiplier — tunable).
   Decision needed by M3.
4. **Cleared-dungeon visual.** When the player exits a cleared dungeon,
   the entrance-tile `Stairs` deco can change to indicate cleared
   state (e.g. dimmer / open-doorway sprite). Proposed: yes, simple
   colour-tint change at render time. Decision needed by M3.
5. **Random encounters during dungeon traversal.** When the player
   walks between cleared rooms, do they ever roll an encounter?
   Proposed: no — encounters live in occupied rooms only; cleared
   rooms stay empty until next world-week (forward-compat for Phase 8
   re-spawn timer). Decision needed by M2.
6. **Per-dungeon-type spawn-kind override granularity.** Should each
   dungeon type have its *own* `spawn_kind_to_template_by_dungeon_type
   _by_zone` (DangerZone × DungeonType combinatorics → 5×5 = 25
   tables), or should DungeonType supersede DangerZone entirely
   (5 tables)? Proposed: supersede — DangerZone is for surface
   wilderness; once you're in a Cult Den the dungeon type defines the
   roster regardless of where the den sits geographically. Decision
   needed by M2.
7. **Locked-door fail consequence.** Failing a lock-pick check:
   spend a move + can't try again? Or spend a move + try again next
   turn? Proposed: try again next turn (no permanent lock-out — the
   tedium is the deterrent, not arbitrary lockout). Decision
   needed by M6.
8. **Trap detection visibility.** Tripwires visible by default, or
   require a Perception check? Proposed: visible to PCs with the
   *Investigation* or *Perception* skill proficiency at all times;
   invisible otherwise (forces non-investigator builds to take damage
   or use a class feature). Decision needed by M6.
9. **HybridParentPicker species filter.** Does the Dam species
   dropdown filter to species *compatible* with the Sire species
   (e.g. mass-class within ±1 size), or does it allow any cross-clade
   pairing? Proposed: allow any cross-clade pairing — `clades.md`
   doesn't impose mass-compatibility rules, and the worldbuilding
   says hybrids exist across all clade pairs (with predictably awkward
   medical compatibility). Decision needed by M0.
10. **Hybrid ability-mod blending revisit.** Phase 6.5 shipped
    auto-accumulation across both parents. M3 ship-point playtests
    the showcase with hybrid PCs at level 3. If the feel is "too
    strong", ship the player-choice picker per Phase 6.5 M4 plan
    (one extra UI step). If neutral, ratify auto-accumulation.
    Decision needed at M3 ship-point.
11. **Auto-fire BetrayalCascade on dialogue-runner-emitted events.**
    Phase 7 M4 wires auto-fire at the *quest-engine* layer. Should
    dialogue-runner-emitted Betrayal events also auto-fire?
    Proposed: yes — the dialogue runner's `rep_event` effect goes
    through the same code path as the quest engine's. The wiring is
    one site (the shared rep_event handler). Decision needed by M4.

---

## 11. What Phase 7 does **not** finish, and why that's OK

Phase 7's exit criterion is: **the player can clear procedurally-
generated dungeons of all five PoI types, fully experience one
hand-tuned Imperium Ruin showcase, replay Act I with real combat
resolutions for Old Howl and Lacroix, and the engine is ready for
Acts II–V to layer their set-piece dungeons on top without
re-architecting any of it. All Phase 6.5 carryover items that block
Phase 7 content are wired.**

Things deliberately deferred:

- **Acts II–V questline content.** Phase 10. Slaughterhouse Raid, Tunnel
  War, Heartstone climax — engine-ready but unauthored.
- **Subclass features at L10 / L15 / L18 / L20.** Phase 9 polish + Phase
  10 content. Schema supports; runtime stubs for non-combat features.
- **Multiclassing.** Phase 9+ if demanded.
- **Custom feats.** Phase 9.
- **Subclass respec.** Phase 9.
- **Full scent propagation simulation across settlements.** Phase 8.
  Scent tags exist on NPCs (Phase 6.5 M6) but they don't *propagate*.
- **NPC schedules / day-night activity.** Phase 8. Lacroix's "night-time"
  framing is a `WorldClock`-gated trigger, not a behaviour schedule.
- **Long/short rest mechanics tied to the world clock.** Phase 8.
  Phase 6.5's "every encounter is fully rested" + "every level-up
  resets per-rest pools" model continues.
- **Pheromone vial crafting.** Phase 8.
- **Trade economy as simulation.** Phase 8.
- **Faction quest lines (Inheritor / Thorn / etc. dedicated arcs).**
  Phase 10.
- **PoI dungeons as procedural multi-room generation with multiple
  floors / stairways.** Schema supports it; no Phase-7 PoI uses it.
  Phase 9 + content packs.
- **Full trap subsystem (pressure plates, runes, gas, alchemy).**
  Phase 8 / 9.
- **Lockpick item economy + crafting.** Phase 8 / 9.
- **Light + fog-of-war + torch radius.** Phase 8 polish.
- **Random encounters during dungeon traversal.** Per §10.5 — Phase 8.
- **Dungeon re-spawn after world-week.** Schema-ready (the
  `PartiallyExplored` field in `DungeonStateSnapshot` is the hook);
  no Phase-7 timer.
- **Cleared-dungeon "trophy" system / kill counts surfaced in UI.**
  Phase 9 polish.
- **Time-based scent-mask expiry.** Stays in Phase 8 (clock-driven).
- **Procedural side-quest generator.** Phase 8 — the dungeon engine
  is the prerequisite, but the quest-template authoring is a separate
  workstream.
- **Quest-driven hostile-NPC spawning at world coordinates** for
  *non-narrative* quests. Phase 7 ships the *capability* (real
  `spawn_npc` effect); Phase 8/9 authors emergent uses.
- **Hybrid character genealogy beyond two purebred parents.** Phase 9+
  if demand surfaces (Phase 6.5 §9.5 already established this scope cap).
- **Multi-settlement hybrid-reveal cascade.** Per-NPC discovery is
  permanent (Phase 6.5 M5); cross-settlement gossip is Phase 8
  propagation.

The payoff: Phase 8 starts on a foundation where character + combat +
settlements + dialogue + quests + factions + dungeons + loot +
levelling + subclasses + hybrids + passing + scent + betrayal are all
real and tested, so the *world simulation* layer (weather, seasons,
NPC schedules, scent propagation, rest mechanics, trade caravan
movement, time-based mask expiry, dungeon re-spawn) can focus on
*time-driven dynamics* instead of co-developing static content at the
same time.

---

## 12. Implementation deviations

This section will be filled in as M0–M6 complete, mirroring the
structure of `theriapolis-rpg-implementation-plan-phase6-5.md` §11.

For each milestone, record a table of:

| Plan said | Shipped | Why |
|---|---|---|
| (one row per deviation) | | |

Plus headline summaries (test-count delta, schema version, files added)
and a "where future agents should look first" pointer set.

The plan body above (§§1–11) is preserved as-written for archival
reference. Future agents touching Phase 7 systems should read **this
§12 first** to know what's actually in code; the plan body is design
intent that may have diverged at implementation time.

*(To be filled in as M0–M6 complete.)*

---

## 13. Where future agents should look first

When picking up a Phase 8+ task that touches Phase 7 systems:

1. Read **§11 (deferred)** + **§12 (deviations, when filled)** to see
   what's *actually* in the code. §12 is the source of truth — the
   plan body above is preserved as written for archival reference.
2. Read [CLAUDE.md](CLAUDE.md) for build/test commands and hard rules.
3. Run `dotnet test` to confirm baseline (target: ~720+ tests at
   Phase 7 close, up from 640 at Phase 6.5 close).
4. Run `dotnet run --project Theriapolis.Tools -- content-validate` to
   confirm content integrity.

When extending dungeon content:

- Author room templates in `Content/Data/room_templates/<type>/`.
  They auto-load via `ContentLoader.LoadRoomTemplates` — no code
  change.
- Run `dotnet run --project Theriapolis.Tools -- content-validate`
  after edits.
- Run `dotnet run --project Theriapolis.Tools -- dungeon-render --seed
  N --poi <id>` for visual QA.

When wiring a new quest with combat:

- `spawn_npc` accepts `anchor:`, `world_tile:`, `dungeon:`,
  `building_role:` target prefixes. See `QuestEngine.RunEffect` for
  the resolver order.
- `start_encounter` in dialogue is the cleanest way to gate combat on
  a player choice. See `millhaven_lacroix.json` for the canonical
  example.
- A `rep_event:Betrayal` effect from quest or dialogue auto-fires the
  Phase 6.5 betrayal cascade if the betrayed NPC is resolvable from
  the actor list. Otherwise (synthetic test events), call
  `BetrayalCascade.Apply` explicitly.

When debugging dungeon generation:

- `dungeon-render` produces a PNG with rooms colour-coded by role
  (entry blue, narrative gold, boss red, dead-end grey).
- `dungeon-walk --steps N` does a deterministic BFS walkthrough and
  prints each room's contents — useful for confirming spawn / loot
  counts match expectations.

When wiring a new L7+ subclass feature:

- Phase 6.5 M2 wired 4 L3 features and Phase 7 M0/M1 wired the
  remaining 12 L3 + 5 L7. Pattern is established.
- Add to `subclasses.json` `feature_definitions` with a `kind` +
  `effect` descriptor.
- Add a switch case in `FeatureProcessor.cs`.
- Add a unit test in `Phase65M2SubclassFeatureTests.cs` or the new
  `SubclassFeatureL7CombatTests.cs`.
- `dotnet run --project Theriapolis.Tools -- character-roll
  --class X --level N` exercises it headless (`--level` flag landed
  in Phase 7 M0).

When wiring a new consumable item:

- Add the item to `items.json` with `kind: "consumable"` and an
  appropriate `consumable_kind` value.
- Add a switch case to `ConsumableHandler.Consume` if the kind is new
  (existing kinds: `healing_potion`, `scent_mask`).
- `InventoryScreen`'s "Use" button routes through `Consume` — no UI
  changes needed for new items in existing kinds.

When debugging hybrid passing or social-stigma surfacing:

- Phase 6.5 M5 stores per-NPC hybrid discovery on
  `pc.Hybrid.NpcsWhoKnow` (PC-side) and `npc.MemoryFlags["knows_hybrid"]`
  (NPC-side; dual-write). `EffectiveDisposition` reads the PC-side set.
- Phase 7 M4 wires `PassingCheck.RollAndApply` into
  `InteractionScreen.OnOpen` — the first-meet check fires once per
  `(pc, npc)` pair.
- The Illegible Body Language and Social Stigma flags are surfaced by
  `InteractionScreen` and consumed by `DialogueRunner` when evaluating
  CHA-tagged checks. See `HybridSocialStigmaTests` for the canonical
  example.

---

*Theriapolis Phase 7 Implementation Plan — 2026-04-29 (rewrite of the 2026-04-27 draft to reflect the post-Phase-6.5 baseline).*
*Author: Claude (Opus 4.7) for LO, in continuity with the Phase 0–6.5 plan series.*
*Phase 6.5 deviation reconciliation in §2; carryover items folded into §8 milestones.*
*Implementation deviations section (§12) to be appended after M0–M6 completion.*
