# Theriapolis — Phase 5 — Design & Implementation Plan
## Rules, Character, Inventory, and Tactical Combat

**Status:** Proposed. Targets the codebase state as of 2026-04-24
(Phase 4 + tile-art polish complete; 256×256 world; `ENABLE_RAIL=false`;
SAVE_SCHEMA_VERSION=4; 114 tests green).

**Governing docs:**
- `theriapolis-rpg-implementation-plan.md` §§ 8.1, 8.2, 8.3 (binding)
- `theriapolis-rpg-clades.md` (content authority for Clades + Species + size)
- `theriapolis-rpg-classes.md` (content authority for Classes + Subclasses + Backgrounds)
- `theriapolis-rpg-equipment.md` (content authority for Items)
- `theriapolis-rpg-implementation-plan-phase4.md` (everything Phase 5 plugs into)

---

## 1. Goals & non-goals

### Goals

1. **A real character.** The player's `Actor` stops being a placeholder and gains
   ability scores, hit points, AC, an inventory, an equipped loadout, a Clade,
   a Species, a Class, and a Background — all chosen on a character-creation
   screen at "New Game" and saved with the world.
2. **Combat that resolves.** Hostile spawns generated by Phase 4's
   `TacticalChunkGen` (already sitting in `TacticalChunk.Spawns` and ignored)
   become live NPCs. When the player and a hostile end up within initiative
   range, control transfers to a turn-based combat loop that runs on the
   tactical grid. Attacks, damage, HP, and death all use a d20-adjacent
   resolver living in `Theriapolis.Core/Rules/Combat/`.
3. **Inventory you can feel.** Equip a weapon → attacks change. Wear armor →
   AC changes. Wrong size → checks suffer. Carry too much → encumbrance
   bites. The size-matching rule from `equipment.md` is a first-class concern,
   not a comment.
4. **Content as data.** Clades, species, classes, items, and NPC templates
   are JSON in `Content/Data/`, loaded by `ContentLoader` the same way
   `biomes.json` is today. New content is a JSON edit, not a code change.
5. **Determinism preserved.** A given (worldSeed, encounterId, turnSequence)
   produces identical dice outcomes. Save mid-combat, load, continue — the
   resolver picks up byte-identical to the live session.
6. **Phase 4 invariants intact.** Polylines stay authoritative. Core stays
   MonoGame-free. All RNG goes through `SeededRng` with new named sub-streams
   declared in `Constants.cs`.

### Non-goals (explicit)

- **Subclasses (level 3+).** Phase 5 ships every class at **level 1 only**.
  The schema and content files carry full level tables for forward
  compatibility, but only level-1 features have runtime effect. Subclass
  selection is reserved for the level-up flow that Phase 5.5 / 6 builds.
- **Levelling beyond character creation.** XP is awarded and persisted, but
  there is no level-up screen and `Character.Level` stays at 1. A `level-up`
  command in Tools is a stretch goal for end-of-phase polish.
- **Hybrid characters.** Optional rules from `clades.md`. Defer to Phase 6.
- **Pheromone Craft, Covenant Authority, Vocalization abilities.** Each is
  a non-trivial subsystem that touches social/scent state Phase 6 owns.
  Their level-1 hooks (e.g. Scent-Broker's *Scent Literacy*, Covenant-Keeper's
  *Mark of the Oath*) ship as **stubs** — they exist, they capture the
  situation, but resolution is deferred. Combat-effect features (Fangsworn
  *Fighting Style*, Bulwark *Sentinel Stance*, Feral *Rage*, Shadow-Pelt
  *Sneak Attack*) ship for real.
- **Faction logic, dialogue, quests, reputation events.** Phase 6.
  `SaveBody.Factions / QuestState / Reputation` stay as the empty
  containers Phase 4 reserved.
- **Friendly NPCs.** `SpawnKind.Merchant` / `Patrol` chunk records are
  loaded but not instantiated as live actors. Only `SpawnKind.WildAnimal`,
  `Brigand`, and `PoiGuard` become combat-capable NPCs. Merchants /
  patrols remain as static map markers; Phase 6 makes them interactive.
- **Long-rest / short-rest mechanics.** Phase 5 treats every combat as
  fully-rested (uses-per-rest features reset between encounters). The full
  rest model — campfires, time-of-day exhaustion, encumbered sleep — is
  Phase 8's problem when the world clock drives diurnal state.
- **PoI interiors / dungeons.** Phase 7. Combat in Phase 5 happens in the
  open world tactical view only.

---

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

Audited 2026-04-24:

| Piece | Where | Phase 5 use |
|---|---|---|
| `Actor` skeleton | [Actor.cs](Theriapolis.Core/Entities/Actor.cs) | Add `Character?` ref + `IsHostileTo(Actor)` helper |
| `PlayerActor` | [PlayerActor.cs](Theriapolis.Core/Entities/PlayerActor.cs) | Extend `CaptureState`/`RestoreState` to include `Character` |
| `ActorManager` | [ActorManager.cs](Theriapolis.Core/Entities/ActorManager.cs) | Add `SpawnNpc`, `RemoveActor`, `Tick(dt)` |
| `TacticalChunk.Spawns` | [TacticalChunk.cs:20](Theriapolis.Core/Tactical/TacticalChunk.cs) | Source for `NpcSpawnerStage` to instantiate live NPCs |
| `SpawnKind` enum | [TacticalChunk.cs:69](Theriapolis.Core/Tactical/TacticalChunk.cs) | Maps to `NpcTemplateDef.Id` (e.g. `Brigand` → `npc.brigand_footpad`) |
| Reserved save fields | [SaveBody.cs:23-30](Theriapolis.Core/Persistence/SaveBody.cs) | Phase 5 leaves these empty (still Phase 6) |
| `IPersistable<T>` | [IPersistable.cs](Theriapolis.Core/Persistence/IPersistable.cs) | New persistables: `EncounterState`, `NpcRosterState` |
| `ContentLoader` | [ContentLoader.cs](Theriapolis.Core/Data/ContentLoader.cs) | Add `LoadClades`, `LoadSpecies`, `LoadClasses`, `LoadItems`, `LoadNpcTemplates` |
| `SeededRng` | [SeededRng.cs](Theriapolis.Core/Util/SeededRng.cs) | New sub-streams: `RNG_CHARACTER`, `RNG_COMBAT`, `RNG_NPC_SPAWN`, `RNG_LOOT` |
| `PlayScreen` | [PlayScreen.cs](Theriapolis.Game/Screens/PlayScreen.cs) | Push/pop `CombatHUDScreen` when an encounter starts; route input to `CombatController` |
| Tactical step counter | [PlayerController.cs:103-104](Theriapolis.Game/Input/PlayerController.cs) | Repurpose: distance-walked drives both clock and **encounter trigger checks** |
| `WorldClock` | [WorldClock.cs](Theriapolis.Core/Time/WorldClock.cs) | Combat suspends world-clock advancement; resumes after |
| Constants | [Constants.cs](Theriapolis.Core/Constants.cs) | All Phase 5 numbers (XP table, encounter trigger radius, AC ceiling, etc.) live here |

Three facts that materially shape Phase 5:

- **Spawns already exist per chunk.** No new generation pass; just an
  *instantiation* pass that happens when `ChunkStreamer` brings a chunk into
  the active window.
- **`SaveBody` has reserved containers** that match the schema for
  Phase 5/6 needs — but `Player` only carries `PlayerActorState`, not
  character data. We extend `PlayerActorState` with `Character` (or add a
  parallel `PlayerCharacterState` field on `SaveBody`). The plan below
  takes the second path so `PlayerActorState` stays a pure positional
  snapshot — clean separation of "where the body is" from "what the body
  is".
- **Tactical combat happens in the same coordinate space as movement.**
  The combat resolver doesn't need a new map — it uses the live
  `ChunkStreamer` window. This is the payoff of Phase 4's coordinate model.

---

## 3. Phase 5 architecture

### 3.1 Module layout (new directories — all greenfield except where noted)

```
Theriapolis.Core/
  Rules/
    Stats/
      AbilityScores.cs        struct  — STR/DEX/CON/INT/WIS/CHA + Mod() helper
      ProficiencyBonus.cs     static  — level → +N table
      SkillId.cs              enum    — 18 skills
      SaveId.cs               enum    — 6 saves + which ability backs each
      DamageType.cs           enum    — bludg/pierce/slash/fire/cold/lightning/poison/psychic/thunder
      Condition.cs            enum    — prone/frightened/restrained/grappled/dazed/exhausted/blinded/stunned/unconscious
      DerivedStats.cs         static  — HP/AC/Initiative/Speed/CarryCap from (clade, species, class, equipment)
    Character/
      Character.cs            class   — runtime aggregate; refs CladeDef + SpeciesDef + ClassDef + ability scores + level + xp + HP curr/max + Inventory
      CharacterBuilder.cs     class   — fluent builder; used by both screen and tests
      Background.cs           record  — id + name (mechanical effects: Phase 6)
    Combat/
      Encounter.cs            class   — owns participants, initiative order, current turn pointer, log, per-encounter SeededRng
      Turn.cs                 struct  — current actor's remaining action / bonus / reaction / movement
      Resolver.cs             static  — pure functions: AttemptAttack, RollDamage, ApplyDamage, MakeSave, ApplyCondition
      AttackProfile.cs        record  — derived from (weapon, attacker, target, situation); immutable per-resolution
      DamageRoll.cs           record  — dice pool + flat mods + type
      LineOfSight.cs          static  — tactical-tile Bresenham; blocked by IsBlockingDeco / wall
      ReachAndCover.cs        static  — size-aware reach, cover from same-size terrain features
  Entities/
    Actor.cs                  EXTEND — add `Character? Character`, `bool IsAlive`, `Faction Allegiance` (placeholder enum)
    NpcActor.cs               class  — Actor + AI state (current target, last-seen position, behavior tree id)
    PlayerActor.cs            EXTEND — CaptureState/RestoreState include character snapshot
    ActorManager.cs           EXTEND — `SpawnNpc(NpcTemplateDef, Vec2)`, `RemoveActor(int)`, `EncounterCheck()` per tick
    Ai/
      BehaviorBrigand.cs      — move toward player, attack when in melee reach
      BehaviorWildAnimal.cs   — attack if engaged, flee at low HP
      BehaviorPoiGuard.cs     — patrol around POI center, engage intruders
      AiContext.cs            — read-only view of map + actors used by behaviors
  Items/
    ItemDef.cs                record — JSON-loaded; id/kind/weight/cost/properties/damage/ac/sizes
    ItemKind.cs               enum   — Weapon, Armor, Shield, Consumable, AdventuringGear, NaturalWeaponEnhancer
    ItemInstance.cs           class  — runtime: ref to ItemDef + qty + condition + (optional) custom name
    Inventory.cs              class  — list of instances + equip-slot map + encumbrance accessor
    EquipSlot.cs              enum   — MainHand, OffHand, Body, Helm, Cloak, Boots, AdaptivePack, NaturalWeaponEnhancer×3
    SizeMatch.cs              static — small/medium/large weapon/armor compatibility check
  Data/
    ContentLoader.cs          EXTEND — LoadClades/LoadSpecies/LoadClasses/LoadItems/LoadNpcTemplates
    CladeDef.cs               record — JSON: id, name, abilityMods, traits[], detriments[], languages[]
    SpeciesDef.cs             record — JSON: id, cladeId, name, size, abilityMods, traits[], detriments[]
    ClassDef.cs               record — JSON: id, name, hitDie, primaryAbility[], saves[], armorProf[], weaponProf[], skillsChoose, skillOptions[], levelTable[]
    SubclassDef.cs            record — JSON: id, classId, name, levelFeatures[] (loaded but unused in Phase 5)
    BackgroundDef.cs          record — JSON: id, name, skillProf[], toolProf[], featureText (mechanical effects deferred)
    NpcTemplateDef.cs         record — JSON: id, name, abilityScores, hp, ac, attacks[], speed, size, behaviorId, lootTableId
  Persistence/
    SaveBody.cs               EXTEND — add `PlayerCharacterState PlayerCharacter`, `List<NpcDelta> ChunkNpcs`, `EncounterState? ActiveEncounter`
    PlayerCharacterState.cs   class  — serializable: clade/species/class/background ids, ability scores, level, xp, hp curr, conditions[], inventory[], equipped slots
    NpcDelta.cs               struct — chunkCoord + localPos + templateId + isAlive + hpCurrent (for surviving NPCs)
    EncounterState.cs         class  — only present mid-combat: encounterId, participants[], initiativeOrder, currentTurnIndex, perEncounterRngState
    SaveCodec.cs              EXTEND — add tag IDs ≥100: TAG_CHARACTER=100, TAG_NPC_ROSTER=101, TAG_ENCOUNTER=102

Theriapolis.Game/
  Screens/
    CharacterCreationScreen.cs  — pre-game: clade → species → class → background → name → confirm
    InventoryScreen.cs          — modal during play (TAB key)
    CombatHUDScreen.cs          — overlay screen pushed when encounter starts; replaces PlayerController input routing
  UI/
    StatBlockPanel.cs           — Myra panel; reusable in CharacterCreation + Inventory + CombatHUD
    InitiativeStrip.cs          — turn order + HP bars
    CombatActionBar.cs          — action buttons (Attack / Move / Use Item / End Turn / etc)
    DamageNumberOverlay.cs      — floating damage numbers in tactical view
  Input/
    CombatController.cs         — replaces PlayerController while CombatHUDScreen is on top

Theriapolis.Tools/Commands/
  CharacterRoll.cs              — `dotnet run -- character-roll --seed N --clade canidae --species wolf --class fangsworn`
  CombatDuel.cs                 — `dotnet run -- combat-duel --seed N --a npc.brigand --b npc.wolf --rounds 20`
  ContentValidate.cs            — sanity check: every species references a real clade, every npc uses real items, every class has level 1 features, etc.

Theriapolis.Tests/
  Rules/
    AbilityScoreTests.cs
    DerivedStatsTests.cs
    LevelTableTests.cs
    CharacterBuilderTests.cs       — every (clade × species × class) combo produces a valid level-1 character
    InventoryEquipTests.cs
    SizeMatchTests.cs
    AttackResolutionTests.cs
    DamageDeterminismTests.cs      — same (encounterSeed, sequence) → identical rolls
    ConditionTests.cs
    LineOfSightTests.cs
    ReachAndCoverTests.cs
  Combat/
    EncounterScenarioTests.cs      — scripted: brigand vs wolf-folk fangsworn → expected outcome at seed X
    InitiativeTests.cs
    AiBehaviorTests.cs
  Persistence/
    CharacterSaveRoundTripTests.cs
    NpcRosterRoundTripTests.cs
    MidCombatSaveRoundTripTests.cs — save mid-encounter, load, finish, expected outcome unchanged
  Architecture/
    CoreNoDependencyTests.cs       EXISTING — extended assertion: Rules/* also has no MonoGame ref

Content/Data/
  clades.json                  — all 7 clades (Canidae, Felidae, Mustelidae, Ursidae, Cervidae, Bovidae, Leporidae)
  species.json                 — all 19+ species per the quick-ref table in clades.md
  classes.json                 — all 8 classes; full level table for forward compat; level-1 features only have runtime effect
  subclasses.json              — content present, mechanics deferred
  backgrounds.json             — all 12 backgrounds; mechanical effects deferred
  items.json                   — Phase 5 subset: ~12 weapons, ~6 armors, ~3 shields, ~6 consumables, ~6 adventuring gear, ~4 natural weapon enhancers
  npc_templates.json           — Phase 5 set: brigand_footpad, brigand_marauder, brigand_archer, wolf, dire_wolf, bear_brown, wolverine, poi_guard_skeletal, poi_guard_corrupted
```

### 3.2 The actor / character split

Keep `Actor` lean (position, facing, speed — what the renderer cares about).
All gameplay state hangs off the optional `Character` ref:

```csharp
public class Actor {
    public int    Id { get; init; }
    public Vec2   Position { get; set; }
    public float  FacingAngleRad { get; set; }
    public float  SpeedWorldPxPerSec { get; set; } = C.PLAYER_TRAVEL_PX_PER_SEC;

    // NEW in Phase 5:
    public Character? Character { get; set; }      // null on actors that aren't combat-capable yet
    public Allegiance Allegiance { get; set; }     // Player | Allied | Neutral | Hostile
    public bool IsAlive => Character?.IsAlive ?? true;
}
```

`PlayerActor` and `NpcActor` both have a non-null `Character`. The
`Character?` nullability is for forward-compat: future actor types
(merchants, scenery NPCs) may not need stat blocks.

This avoids a single mega-class and keeps the rendering layer ignorant of
the stat layer. `Camera2D` doesn't need to know what HP is.

### 3.3 Coordinate systems for combat

Combat happens entirely in **tactical-tile space**, which is the same as
**world-pixel space** (1 tactical tile = 1 world pixel — Phase 4
contract). Reach, range, and movement are measured in tactical tiles.

Size-to-occupancy mapping (from `clades.md` size appendix):

| Size       | Tactical-tile footprint | Reach (melee) |
|------------|-------------------------|---------------|
| Small      | 1×1                     | 1 tile        |
| Medium     | 1×1                     | 1 tile        |
| Medium-Large | 1×1 (counts as Large for grappling/carrying) | 1 tile |
| Large      | 2×2                     | 2 tiles       |

(The plan reserves 3×3 for Huge but no Phase 5 NPC uses it.)

This is a deliberate simplification: in tabletop terms a "5 ft. reach"
weapon = 1 tile, and "10 ft. reach" / Large body = 2 tiles. Anything
finer (15 ft. reach polearms, etc.) becomes a weapon `Reach: 2` tag in
its `ItemDef`.

### 3.4 Combat encounter lifecycle

```
Player walks tactical → ChunkStreamer hands ActorManager the chunk's
spawned NPCs (instantiated lazily on first stream-in).

Per tick (PlayScreen.Update):
  ActorManager.EncounterCheck():
    For each HOSTILE NPC within ENCOUNTER_TRIGGER_TILES (default 8) of
    player AND with line of sight:
      → start an Encounter (auto-triggered)
      → push CombatHUDScreen
      → halt PlayerController, hand input to CombatController
      → halt WorldClock advance until encounter ends

  ActorManager.NeutralProximityCheck():
    For each FRIENDLY/NEUTRAL NPC within INTERACT_PROMPT_TILES (default 2)
    of player AND with line of sight:
      → show "Press F to interact with <name>" prompt overlay
      → on F press → InteractionScreen (Phase 6 attaches dialogue)

While CombatHUDScreen is on top:
  CombatController drives the active actor's Turn:
    Player turn: input → AttackProfile → Resolver.AttemptAttack → log + damage numbers
    NPC turn: behavior  → AttackProfile → Resolver.AttemptAttack → log + damage numbers
  When all hostiles are dead OR all hostiles are out of sight + 3 turns:
    → encounter ends
    → resolve XP award (player only) and loot drops
    → pop CombatHUDScreen
    → resume WorldClock + PlayerController
```

**Two distinct interaction models, by allegiance:**

- **Hostile** → auto-trigger combat on LOS-within-range. The player
  doesn't need to press anything — walking into a brigand's sight line
  *is* the engagement. This matches CRPG convention and removes a
  finicky "press F to attack" step in a continuous-movement view.
- **Friendly / Neutral** → walk-up prompt. A small overlay
  (`"[F] Talk to Mara the Innkeeper"`) appears when the player gets
  within 2 tactical tiles. Pressing F opens an `InteractionScreen`.
  Phase 5 ships the prompt + screen *shell*; the screen body is empty
  (placeholder text) until Phase 6 attaches dialogue. This carves out
  the input path now so Phase 6 doesn't have to refactor encounter
  triggering.

`Allegiance` is set per-NPC at instantiation from
`NpcTemplateDef.DefaultAllegiance`. Brigands / Wild Animals / PoI
Guards default Hostile; Merchants / Patrols default Neutral. Future
faction logic can mutate this at runtime (Phase 6).

### 3.5 The dice contract

All combat dice go through a per-encounter `SeededRng` keyed by:

```
encounterSeed = worldSeed
              ^ C.RNG_COMBAT
              ^ EncounterId
```

`EncounterId` = `(chunkCoord.X << 32) | chunkCoord.Y << 16 | encounterIndexInChunk`.
This means:

- The **same** encounter triggered the **same** way in two playthroughs at
  the same seed produces identical first-roll outcomes.
- Player choices (which target, which weapon) immediately diverge the
  state, so realistic playthroughs see independent outcomes after move 1.
- Save mid-combat captures `currentTurnIndex` + `nextRollSequence` →
  `Encounter.RollD20()` and friends pull from
  `SeededRng(encounterSeed).Skip(nextRollSequence).NextUInt64()` →
  byte-identical resume.

This is the same pattern Phase 4 uses for chunk gen. New constants:

```csharp
public const ulong RNG_CHARACTER  = 0xC4A2AC7EUL;  // ability score rolls + starting inventory
public const ulong RNG_COMBAT     = 0xC0B47UL;     // per-encounter dice
public const ulong RNG_NPC_SPAWN  = 0xA7C2AUL;     // template variation when instantiating from chunk spawn list
public const ulong RNG_LOOT       = 0x107EUL;      // post-encounter drops
```

---

## 4. Subsystem detail

### 4.1 Stats

`AbilityScores` is a 6-byte struct (each score 1–30; level-1 cap 18 before
clade/species mods, hard cap 20 before level-20 features). `Mod(int score)`
returns `(score - 10) / 2` rounded toward negative infinity per d20 rules.

`DerivedStats` is computed from the live `Character` and re-runs whenever
a relevant input changes (level up, equip change, condition applied):

| Stat | Formula |
|---|---|
| MaxHP    | `class.HitDie + Mod(CON)` at level 1; later levels add `RollHitDie() + Mod(CON)` (rolled at level-up time, then frozen) |
| AC       | `armor.BaseAc + min(Mod(DEX), armor.MaxDexBonus) + shield.AcBonus + featureBonuses` |
| Initiative | `Mod(DEX) + featureBonuses` (e.g. Feral L7 adds Mod(WIS)) |
| Speed    | `species.BaseSpeed + classMods + conditionMods` (clamped ≥ 0) |
| CarryCap | `STR * 15` lb (halved for Small, doubled for Large per equipment.md) |

Skill checks: `1d20 + Mod(stat) + (proficient ? ProfBonus : 0)`. Tools
can run an interactive `character-roll` to dump every roll.

### 4.2 Character creation

Screen flow (single Myra panel, multi-step):

1. **Clade.** Picker of 7 cards: Canidae / Felidae / Mustelidae / Ursidae /
   Cervidae / Bovidae / Leporidae. Each shows ability mods + 1-line trait
   summary.
2. **Species.** Filtered to the selected clade. Shows size, ability mods,
   defining trait.
3. **Class.** All 8. Shows hit die, primary ability, level-1 feature
   names. Recommends species/class fits (informational only — no
   restrictions).
4. **Background.** All 12. Skill proficiencies + tool proficiencies +
   feature text. Mechanical effects of features are stubbed for Phase 5.
5. **Stats.** Two methods, player choice:
   - **Standard array:** 15, 14, 13, 12, 10, 8 (assignable). Default.
   - **Roll 4d6 drop lowest** ×6, assign. Each *Reroll* press derives a
     fresh seed:
     ```
     statRollSeed = worldSeed ^ RNG_STAT_ROLL ^ msSinceGameStart
     ```
     `msSinceGameStart` is wall-clock ms since the process started
     (`Stopwatch.GetTimestamp` snapshot at game launch).
     `CharacterBuilder.RollAbilityScores` accepts an
     `ulong? msOverride = null` parameter; when non-null, that value is
     used in place of the live ms snapshot. Tests use the override; the
     game does not. This gives true non-reproducibility across plays
     (player gets fresh dice every press), reproducibility within tests
     (fixed override → fixed roll), and worldseed-anchored variation
     (the same ms snapshot in two different worlds yields different
     rolls because `worldSeed` is XORed in).
6. **Skills.** Class lists `skillsChoose` and `skillOptions`. Player
   picks N from the offered list (in addition to background's two free).
7. **Name + confirm.** Default "Wanderer". On confirm: `CharacterBuilder`
   produces a `Character`, `ActorManager.SpawnPlayer` is called with
   that character attached.

**Validation:** every step's Next button is disabled until the field is
valid. `CharacterBuilder.TryBuild(out string error)` is the single
canonical check.

### 4.3 Inventory + equipment

```csharp
public sealed class Inventory {
    public List<ItemInstance> Items { get; } = new();
    public Dictionary<EquipSlot, ItemInstance?> Equipped { get; } = new();
    public float TotalWeight => Items.Sum(i => i.Def.Weight * i.Qty);
    public bool TryEquip(ItemInstance item, EquipSlot slot, out string err);
    public bool TryUnequip(EquipSlot slot, out string err);
}
```

Equip slots:

| Slot | Notes |
|---|---|
| MainHand     | Weapon or two-handed weapon |
| OffHand      | Shield, light weapon (dual-wield), or empty |
| Body         | Armor (Light/Medium/Heavy) or none |
| Helm         | Optional |
| Cloak        | Optional |
| Boots        | Optional |
| AdaptivePack | Backpack (raises CarryCap) |
| NaturalWeapon×3 | Fang Caps / Claw Sheaths / Hoof Plates / Antler Tips / Horn Rings (slot N for which natural-weapon location they augment) |

`TryEquip` enforces:

- Weapon proficiency (class.WeaponProf must contain the item's
  proficiency tag, or the wielder takes disadvantage on attacks).
- Armor proficiency (no proficiency = disadvantage on STR/DEX checks +
  no spellcasting if applicable; for Phase 5, just disadvantage).
- Size compatibility: `SizeMatch.Check(item.Sizes, character.Size)`. If
  no match and item is **not** Adaptive, equip succeeds but sets a
  `WrongSize` flag → disadvantage on attack rolls / -2 AC.
- Two-handed weapons clear the OffHand slot.
- Heavy armor STR requirement (item.MinStr).

Encumbrance: at >100% capacity → speed -10 ft.; >150% → speed halved
+ disadvantage on STR/DEX/CON checks.

### 4.4 Combat resolver

`Resolver` is a static class with pure functions. The `Encounter` is the
only stateful object; everything else is computed from `(Encounter,
attackerId, targetId, abilityKey, rngSequence)`.

**`AttemptAttack(encounter, attacker, target, weapon, situation)`:**

1. Compute `AttackProfile` (attack bonus, damage dice, type, reach).
2. Roll d20 (advantage/disadvantage from `situation` flags).
3. Compare to `target.AC`. Critical hit on natural 20 (or 19-20 for
   crit-range weapons — e.g. Razored Claw Sheaths).
4. On hit: roll damage dice (doubled on crit per d20 rules — *only the
   dice double, not the modifiers*).
5. Apply target HP reduction.
6. Append a `CombatLogEntry` with dice values, modifiers, and outcome.

**`MakeSave(encounter, target, saveId, dc)`:** d20 + ability mod + (prof
if proficient). Returns `bool` and logs.

**`ApplyCondition(encounter, target, condition, durationRounds)`:** sets
the condition; duration ticks down at end of target's turn. Conditions
modify subsequent `AttackProfile` / `MakeSave` evaluations through
`SituationFlags`.

**`Death`:** Phase 5 ships **permadeath** as the only death model.

- NPC at HP ≤ 0 → removed from `ActorManager` immediately; chunk's
  `NpcDelta` records the kill so the body stays gone after a save/load
  cycle.
- Player at HP ≤ 0 → start death saves (1d20 every turn; 3 cumulative
  successes ≥ 10 = stabilised at 0 HP, recoverable to 1 HP via ally's
  Field Repair / Healer's Kit; 3 cumulative failures < 10 = **death**).
  A natural 20 on a death save = revive at 1 HP. A natural 1 = two
  failures. (Standard d20 death-save loop.)
- On player death: an unrecoverable `DeathScreen` appears with a single
  option, `[Return to title]`. The player's most recent save (theirs, or
  the encounter-start autosave) is the recovery mechanism — there is no
  in-world respawn or resurrection in Phase 5.

**Save-anywhere is the player's hedge against permadeath.** The pause
menu (ESC during play) gains a `Save Game` action that writes to the
current slot at any time *except* during cut scenes. Phase 5 has no
cut scenes, so save-anywhere is unconditional in this phase — the
exclusion is forward-compat scaffolding for whichever phase introduces
narrative interrupts. Combined with the existing autosave-on-screen-
transition (Phase 4), the player can save right before an ambush, lose,
and reload to retry. Permadeath here means "no in-world respawn", not
"no second chances".

**Save-anywhere wiring:**
- Pause menu adds `Save Game` button → calls `SaveCodec.Write(currentSlot)`.
- New autosave point: combat-start writes to slot `autosave_combat`,
  separate from the rolling autosave slot, so the player can always
  retry the most recent fight even if their manual save is older.
- `EncounterState` already round-trips per §4.7 — saving mid-combat
  works as a side effect of save-anywhere.

### 4.5 NPC + AI

Templates are JSON. Behaviors are code (small switch statement, not a
behavior-tree library — keep dependencies down):

```csharp
public interface INpcBehavior {
    void TakeTurn(NpcActor self, Encounter enc, AiContext ctx);
}
```

Three behaviors ship in Phase 5:

| Behavior | Logic |
|---|---|
| `Brigand`     | If has ranged weapon and target > 4 tiles → ranged attack. Else move toward target along walkable tactical tiles, attack when in reach. |
| `WildAnimal`  | Identical to `Brigand` but flees (move directly away from target) when at < 25% HP. |
| `PoiGuard`    | Patrol around POI center while no target in sight. Engage like `Brigand` once target enters sight. |

`AiContext` provides the read-only view: tactical tiles in current chunk
window, other actors, line of sight via `ReachAndCover.LineOfSight`. AI
**must not** mutate the world directly; it returns intent and the
`Encounter` resolves it through `Resolver`.

### 4.6 Difficulty scaling — danger-zone template selection

Phase 3 already produces an `EncounterDensity` map (Stage 20). Phase 5
layers a **DangerZone** index on top, computed once per chunk at
instantiation time:

```csharp
int DangerZone(WorldTile homeTile, ChunkCoord c) {
    int distFromStartTiles    = ChebyshevDist(c.WorldTileCenter, world.PlayerStartTile);
    int distFromRoadTiles     = NearestPolylineDistance(c.WorldTileCenter, world.Roads);
    int distFromSettlementTiles = NearestSettlementDistance(c.WorldTileCenter);

    int zone = 0;
    zone += distFromStartTiles      / C.DANGER_DIST_FROM_START_PER_ZONE;       // +1 per 50 tiles
    zone += (distFromRoadTiles      > C.DANGER_DIST_FROM_ROAD_THRESHOLD) ? 1 : 0;
    zone += (distFromSettlementTiles > C.DANGER_DIST_FROM_SETTLE_THRESHOLD) ? 1 : 0;
    zone += BiomeDangerBonus(homeTile.Biome);   // grassland 0, forest +0, mountain +1, marsh +1, badlands +2
    return Math.Clamp(zone, C.DANGER_ZONE_MIN, C.DANGER_ZONE_MAX);
}
```

Five zones (0 = safest, 4 = deepest wilds). Each `SpawnKind` consults a
**zone → template id** lookup table living in `npc_templates.json`:

```jsonc
{
  "spawn_kind_to_template_by_zone": {
    "Brigand": [
      "brigand_footpad",     // zone 0: weakest
      "brigand_footpad",
      "brigand_marauder",
      "brigand_marauder",
      "brigand_captain"      // zone 4: deepest wilds
    ],
    "WildAnimal": [
      "wolf_pup",
      "wolf",
      "wolf",
      "dire_wolf",
      "bear_brown"           // zone 4: bears in the deep wilds
    ]
  }
}
```

This means **NPC stats themselves are fixed per template** (no
per-instance scaling) — what varies is *which template* spawns where.
Easier to balance, easier to test, easier to communicate to the player
("the deep woods have bears" rather than "bears here have +20% HP").

`DangerZone` is computed once per chunk at instantiation and stored on
`TacticalChunk.DangerZone` (new byte field). The chunk's hash includes
it, so determinism tests catch any change to the zone formula.

`combat-duel` Tools command grows a `--zone N` flag for testing the
template lookup at a specific danger level.

### 4.7 Save schema

Two new fields on `SaveBody`:

```csharp
public PlayerCharacterState  PlayerCharacter { get; set; } = new();
public List<NpcDelta>        ChunkNpcs       { get; set; } = new();
public EncounterState?       ActiveEncounter { get; set; }   // null when not in combat
```

New `SaveCodec` tags (≥100 per the Phase 4 forward-compat rule):

```
TAG_CHARACTER     = 100   // PlayerCharacterState
TAG_NPC_ROSTER    = 101   // List<NpcDelta>
TAG_ENCOUNTER     = 102   // EncounterState? (writes only if present)
```

Bump `SAVE_SCHEMA_VERSION` to **5**. Add a no-op migration `V4ToV5`:
empty character → game refuses to load and prompts player to start a
new game from the same seed (so existing Phase-4 saves don't crash, but
they also don't auto-promote to Phase-5 saves with no character).

`PlayerCharacterState` is a flat record of primitives + arrays — same
discipline as `PlayerActorState`. `Inventory` serializes as a list of
`(itemId, qty, equipSlot?)` triples.

`ChunkNpcs` is per-chunk override list: any chunk whose NPCs have
diverged from the deterministic `Pass5_Spawns` baseline (one or more
killed; HP changed mid-chunk) shows up here. Empty list = baseline
roster intact. Flushed to save at chunk eviction or autosave.

`ActiveEncounter` is the rare case: player saved during combat. Loading
restores the encounter mid-state and pushes `CombatHUDScreen` directly
from the load path.

### 4.8 Content schemas

```jsonc
// clades.json
{
  "id": "canidae",
  "name": "Canidae",
  "ability_mods": { "CON": 1, "WIS": 1 },
  "size": null,                     // size lives on species, not clade
  "traits": [
    { "id": "pack_instinct", "name": "Pack Instinct", "description": "..." },
    { "id": "superior_scent", "name": "Superior Scent", "description": "..." }
  ],
  "detriments": [
    { "id": "pack_dependent",   "name": "Pack-Dependent",   "description": "..." }
  ],
  "languages": ["common", "canid"]
}

// species.json
{
  "id": "wolf",
  "clade_id": "canidae",
  "name": "Wolf-Folk",
  "size": "medium_large",
  "ability_mods": { "STR": 1 },
  "base_speed_ft": 30,
  "traits": [
    { "id": "jaws_of_the_alpha", "natural_weapon": { "name": "Bite", "damage": "1d8", "type": "piercing", "scaling_levels": [5, 11] } },
    { "id": "tireless_pursuit", "description": "..." },
    { "id": "howl",             "description": "...", "uses_per_long_rest": 1 }
  ],
  "detriments": [...]
}

// classes.json
{
  "id": "fangsworn",
  "name": "Fangsworn",
  "hit_die": 10,
  "primary_ability": ["STR", "DEX"],
  "saves": ["STR", "CON"],
  "armor_proficiencies": ["light", "medium", "heavy", "shields"],
  "weapon_proficiencies": ["simple", "martial", "natural"],
  "skills_choose": 2,
  "skill_options": ["athletics", "intimidation", "perception", "survival", "animal_handling"],
  "level_table": [
    { "level": 1, "prof": 2, "features": ["fighting_style", "claw_and_steel"] },
    { "level": 2, "prof": 2, "features": ["action_surge_1"] },
    /* ...through level 20... */
  ],
  "feature_definitions": {
    "fighting_style": { "kind": "choice", "options": ["fang_and_blade", "shieldwall", "duelist", "great_weapon", "natural_predator"] },
    "claw_and_steel": { "kind": "passive", "effect": "combine_natural_and_weapon_attacks" },
    "action_surge_1":  { "kind": "active",  "uses_per_short_rest": 1, "effect": "extra_action" }
  }
}

// items.json — Phase 5 v1 set (~30 items)
{
  "id": "rend_sword",
  "kind": "weapon",
  "name": "Rend-sword",
  "cost_fang": 25,
  "weight_lb": 3,
  "sizes": ["medium", "large"],
  "proficiency": "martial",
  "damage": "1d8",
  "damage_versatile": "1d10",
  "damage_type": "slashing",
  "properties": ["versatile"]
}

// npc_templates.json
{
  "id": "brigand_footpad",
  "name": "Brigand Footpad",
  "size": "medium",
  "ability_scores": { "STR": 11, "DEX": 14, "CON": 12, "INT": 10, "WIS": 10, "CHA": 9 },
  "hp": 11,
  "ac": 12,
  "speed_ft": 30,
  "attacks": [
    { "name": "Scruff-knife", "to_hit": 4, "damage": "1d4+2", "type": "slashing", "reach_tiles": 1 }
  ],
  "behavior": "brigand",
  "loot_table": "loot_brigand_low",
  "xp_award": 25
}
```

`ContentValidate` Tools command verifies referential integrity at build
time and fails CI on any broken reference.

---

## 5. Determinism & RNG

Every dice roll's source must be reproducible.

| RNG sub-stream | Used by |
|---|---|
| `RNG_CHARACTER` | Character-creation 4d6-drop-lowest rolls; starting equipment variation |
| `RNG_NPC_SPAWN` | Per-NPC variation when a chunk's spawn list is instantiated (e.g. brigand's stat-block rolls) |
| `RNG_COMBAT`    | All in-combat dice (attack rolls, damage, saves, condition durations) |
| `RNG_LOOT`      | Post-encounter drops (loot table evaluation) |

Per-encounter sub-seed:
`encounterSeed = worldSeed ^ RNG_COMBAT ^ encounterId`.

The encounter's `SeededRng` advances monotonically. `EncounterState`
serializes `(encounterSeed, nextRollSequence)`; resume re-creates the
RNG and skips to that sequence. Verified by
`MidCombatSaveRoundTripTests`.

**Tests required:**

- `DamageDeterminismTests` — 1000 rolls from `(seedA, seq=0..999)`
  produce the same outputs across 5 process runs.
- `MidCombatSaveRoundTripTests` — scripted encounter, save at turn 3,
  load, complete; final HP/log identical to running it through to
  completion in one go.

---

## 6. Constants going into `Constants.cs`

```csharp
// ── Phase 5: RNG sub-streams ──────────────────────────────────────────
public const ulong RNG_CHARACTER = 0xC4A2AC7EUL;
public const ulong RNG_STAT_ROLL = 0x57A7507UL;       // 4d6-drop-lowest re-rolls in char creation
public const ulong RNG_COMBAT    = 0xC0B47UL;
public const ulong RNG_NPC_SPAWN = 0xA7C2AUL;
public const ulong RNG_LOOT      = 0x107EUL;

// ── Phase 5: Encounter triggering ────────────────────────────────────
public const int   ENCOUNTER_TRIGGER_TILES   = 8;     // tactical tiles; LoS still required (hostiles)
public const int   INTERACT_PROMPT_TILES     = 2;     // tactical tiles; LoS still required (friendly/neutral)
public const int   ENCOUNTER_DISENGAGE_TILES = 16;    // hostiles out of sight + this far → encounter ends
public const int   ENCOUNTER_DISENGAGE_TURNS = 3;     // ...for this many consecutive turns

// ── Phase 5: Combat ──────────────────────────────────────────────────
public const int   AC_FLOOR  = 5;                     // sanity clamp
public const int   AC_CEILING = 30;
public const int   HP_MAX    = 999;                   // Phase 5 won't exceed this
public const int   DEATH_SAVES_TO_DIE    = 3;
public const int   DEATH_SAVES_TO_STABLE = 3;
public const int   CRIT_NATURAL = 20;

// ── Phase 5: Encumbrance ─────────────────────────────────────────────
public const float ENCUMBRANCE_SOFT_MULT = 1.0f;      // ≥1.0× capacity → speed -10ft
public const float ENCUMBRANCE_HARD_MULT = 1.5f;      // ≥1.5× capacity → speed halved + disadvantage

// ── Phase 5: Difficulty scaling (danger zones) ───────────────────────
public const int   DANGER_DIST_FROM_START_PER_ZONE   = 50;   // tiles per +1 zone
public const int   DANGER_DIST_FROM_ROAD_THRESHOLD   = 8;    // tiles; further than this = +1 zone
public const int   DANGER_DIST_FROM_SETTLE_THRESHOLD = 16;   // tiles; further than this = +1 zone
public const int   DANGER_ZONE_MIN = 0;
public const int   DANGER_ZONE_MAX = 4;

// ── Phase 5: Save ────────────────────────────────────────────────────
// SAVE_SCHEMA_VERSION bumped to 5 (was 4 in Phase 4)
public const string SAVE_SLOT_AUTOSAVE_COMBAT = "autosave_combat";  // separate slot for retry-last-fight
```

---

## 7. Milestones

Each is a ship point: a branch with a self-contained set of changes,
green tests, and a feature you can demonstrate.

**M1 — Stats core + content load.**
- `Rules/Stats/*` complete with tests.
- `ContentLoader.LoadClades / LoadSpecies / LoadClasses / LoadItems / LoadNpcTemplates`.
- Author all 7 clades.json, all 19 species.json, all 8 classes.json
  (level tables included; only level-1 feature *defs* present), all 12
  backgrounds.json, the Phase 5 items.json subset, the Phase 5
  npc_templates.json set.
- `ContentValidate` Tools command green.
- **Ship point:** `dotnet test` passes new Rules/* suite. `dotnet run --
  content-validate` exits 0.

**M2 — Character creation + character-as-Actor-attachment + save-anywhere.**
- `Character`, `CharacterBuilder`, `Inventory`, `ItemInstance`,
  `EquipSlot`, `SizeMatch` in Core.
- `Actor.Character` field added; `PlayerActor.CaptureState` /
  `RestoreState` extended.
- `CharacterCreationScreen` in Game (Myra-driven multi-step panel)
  including Standard Array + 4d6-drop-lowest with `RNG_STAT_ROLL` ^
  `msSinceGameStart` seeding (override path wired for tests).
- `character-roll` Tools command for headless verification, with
  `--ms-override N` flag.
- Save schema bumped to v5; `V4ToV5` migration shipped (refuses Phase-4
  saves with a clear message).
- **Pause-menu Save Game button** wired (save-anywhere). Cut-scene
  exclusion is forward-compat (no cut scenes in Phase 5 → unconditional).
- **Ship point:** New Game → character creation → enter the world with
  a real stat block. ESC → Save Game → quit → load → same character at
  the same position.

**M3 — Inventory UI + equip mechanics.**
- `InventoryScreen` (TAB key) with drag-to-equip slots.
- `Equipped[*]` flows through to `DerivedStats.AC` and the future
  attack profile.
- Encumbrance speed effect visible in tactical movement (slower if
  overweight).
- **Ship point:** Equip a chain shirt → AC visibly changes on the
  character sheet panel. Pick up rocks until encumbered → walk slower.

**M4 — Combat resolver + scripted-encounter Tools command.**
- `Encounter`, `Turn`, `Resolver`, `AttackProfile`, `DamageRoll`,
  `LineOfSight`, `ReachAndCover`.
- `combat-duel` Tools command: `--a player_character.json --b
  brigand_footpad --seed 42 --rounds 20` runs a scripted melee and
  prints the log.
- All `Rules/Combat/*` tests green including
  `DamageDeterminismTests`.
- **No game-side integration yet.** Pure Core + Tools.
- **Ship point:** combat resolver verifiable in isolation. Headless
  scripted scenarios produce expected log lines.

**M5 — Combat in the live game + danger zones + interact prompt.**
- `NpcActor`, `INpcBehavior` + 3 behaviors (Brigand, WildAnimal,
  PoiGuard).
- `ActorManager.SpawnNpc` called by a new `NpcInstantiationStage` that
  runs when `ChunkStreamer` brings a chunk into the active window. The
  stage consults the chunk's `DangerZone` to pick the actual template
  from the `spawn_kind_to_template_by_zone` table — same `SpawnKind`,
  different stat block per zone.
- `EncounterCheck` (hostiles auto-trigger) and `NeutralProximityCheck`
  (friendly/neutral show `[F] Talk to ...` prompt) per
  `PlayScreen.Update` tick.
- `CombatHUDScreen`, `CombatController`, `InitiativeStrip`,
  `CombatActionBar`, `DamageNumberOverlay`.
- `InteractionScreen` shell — Phase 5 ships an empty placeholder body
  ("This NPC has nothing to say yet — Phase 6 attaches dialogue."), but
  the input wiring is real so Phase 6 doesn't refactor.
- Mid-encounter `EncounterState` save/restore via new
  `IPersistable<EncounterState>` on `Encounter`.
- Combat-start autosave to `SAVE_SLOT_AUTOSAVE_COMBAT` so a fresh
  retry-this-fight slot is always available.
- `MidCombatSaveRoundTripTests` green.
- **Ship point:** walk into a brigand-spawn area in tactical mode →
  encounter starts → fight to the death → world resumes. Walk up to a
  merchant in town → `[F] Talk` prompt appears → press F → empty
  dialogue placeholder. Save mid-fight, load, continue, identical
  outcome. Walk further from the start to verify deeper-zone NPCs
  (dire wolves, captain brigands) actually appear.

**M6 — Polish + first-pass class features.**
- Implement all level-1 *combat-touching* class features with real
  effects:
  - Fangsworn — `Fighting Style` (5 options), `Claw & Steel`.
  - Bulwark — `Sentinel Stance` (bonus action stance), `Guardian's
    Mark`.
  - Feral — `Feral Rage` (2/encounter; uses-per-short-rest treated as
    per-encounter in Phase 5), `Unarmored Defense`.
  - Shadow-Pelt — `Sneak Attack` (1d6), `Scent Discipline` (passive,
    no scent-system effect yet — flags only).
  - Scent-Broker — `Scent Literacy` (UI surfaces NPC clade and current
    HP%), `Nose for Lies` (stub: logs "lie detected" with no dialogue
    system).
  - Covenant-Keeper — `Mark of the Oath` (combat: marked targets give
    +2 attack to allies — hookable into faction logic later).
  - Muzzle-Speaker — `Voice of the Pack` (ally within 30ft gains
    +1d4 to next attack; bonus action; per-encounter use).
  - Claw-Wright — `Field Repair` (action: heal 1d8 + INT to ally or
    self), `Adaptive Crafting` (out-of-combat: removes WrongSize
    penalty for one item, 1-hour rest).
- Death-save loop (player only — d20 per turn, 3 successes ≥ 10 to
  stabilise, 3 failures < 10 = permadeath).
- `DeathScreen` on permadeath: single `[Return to title]` button. No
  in-world respawn. Recovery is via load (the autosave_combat slot
  always has a freshly-pre-encounter checkpoint, so retry-last-fight
  is one menu away).
- Loot drop on NPC death (single weighted roll into chunk's local
  ground tiles; pickup via TAB inventory).
- **Ship point:** a playable Phase 5. Roll a Wolf-Folk Fangsworn,
  walk into Millhaven, fight a brigand, win, loot the body, save,
  level-1 features all visibly working.

---

## 8. Risks & mitigations

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Content-creation surface explodes (8 classes × 19 species × full level tables) | High | Med | Schema is forward-compatible from day one — author full level tables but only wire level-1 features. Phase 5.5 fills in higher levels without schema churn. |
| Combat resolver UX feels chunky after Phase 4's smooth tactical movement | Med | High | M4 + M5 split deliberately: prove the resolver in headless before integrating. Iterate UX in M6 with the live system in hand. |
| Mid-combat save/load determinism breaks subtly | Med | High | `MidCombatSaveRoundTripTests` is gating from M5 onward. Per-encounter SeededRng + monotonic sequence is the only way dice flow — no `Random.Shared`, no time-based seeds. |
| AI loops or stalls in chunk geometry the player rarely visits | Med | Med | Each NPC behavior has a per-turn budget (no recursion, no while-true). `INpcBehavior.TakeTurn` returns within bounded time; if no valid action, NPC ends turn. Tested via `AiBehaviorTests`. |
| Size + reach + cover interact in ways that break tactical clarity | Med | Med | Cover model is intentionally minimal in Phase 5: same-size deco between attacker and target = ½ cover (-2 attack); larger deco = ¾ cover (-5 attack). Anything fancier is Phase 6. |
| Scope of "level 1 class features" balloons because some level-1 features depend on subsystems Phase 5 doesn't have | Med | Med | M6's checklist explicitly lists which features are real-effect vs stubs. Anything that needs scent / faction / dialogue ships as a stub now and is upgraded by the phase that owns the missing system. |
| `SAVE_SCHEMA_VERSION=5` breaks all Phase-4 saves with no recovery path | Low | Low | By design — Phase-4 saves have no character. Migration prompts player to start a new game from the same seed, preserving worldgen identity. The world is reproducible from the seed; only the placeholder actor is lost. |

---

## 9. Resolved decisions

The seven open questions from the v1 draft were closed on 2026-04-24:

1. **Stat-roll method.** Standard Array default; 4d6-drop-lowest as an
   opt-in `Reroll` button. Each roll is seeded by
   `worldSeed ^ RNG_STAT_ROLL ^ msSinceGameStart`. Tests pass an
   `ulong? msOverride` parameter to `CharacterBuilder.RollAbilityScores`
   to pin the roll. → Wired in §4.2.
2. **Encounter trigger model.** Hostile NPCs auto-trigger combat on
   LOS-within-`ENCOUNTER_TRIGGER_TILES`. Friendly / Neutral NPCs show
   a `[F] Talk to ...` prompt within `INTERACT_PROMPT_TILES` and open
   an `InteractionScreen` when F is pressed. The screen body is a
   placeholder in Phase 5 (Phase 6 attaches dialogue). → Wired in §3.4.
3. **Player death.** Permadeath. The death-save loop runs (3 successes
   ≥ 10 to stabilise, 3 failures < 10 = dead). On death, `DeathScreen`
   offers only `[Return to title]`. **Save-anywhere** is the player's
   hedge: the pause menu's `Save Game` works at any moment except cut
   scenes (forward-compat — no cut scenes exist in Phase 5). A
   dedicated `SAVE_SLOT_AUTOSAVE_COMBAT` slot writes at every
   encounter-start so retry-last-fight is one load away. → Wired in
   §4.4 and §6.
4. **Difficulty scaling.** Per-chunk `DangerZone` (0–4) computed from
   biome danger + distance from player start + distance from
   roads/settlements. The chunk's `DangerZone` selects which template
   each `SpawnKind` uses (table in `npc_templates.json`). NPC stats per
   template stay fixed — what varies is *which template* spawns.
   → Wired in new §4.6.
5. **XP table.** D&D 5e standard table verbatim. Level 2 at 300 XP,
   level 3 at 900 XP, etc. XP awarded and persisted in Phase 5; the
   level-up screen ships in Phase 5.5 / 6.
6. **Loot generosity.** Each `NpcTemplateDef` references a `loot_table`
   id; Phase 5 ships ~5 tables that lean stingy. Tune by playtest in M6
   using a `combat-duel`-style loot-distribution dump.
7. **Natural-weapon enhancers + ability stat.** Natural-weapon attacks
   default to STR. Fangsworn's *Natural Predator* fighting style
   upgrades all natural-weapon attacks to "use the higher of STR or
   DEX" (no new tag — handled inside the resolver when computing the
   attack profile).

---

## 10. What Phase 5 does **not** finish, and why that's OK

Phase 5's exit criterion is: **a complete level-1 character can be created,
walks the world, fights and survives (or dies and loads), and the system
is ready to layer leveling, social/scent abilities, factions, and
quests on top without re-architecting any of it.**

Things deliberately deferred:

- **Levelling beyond level 1.** Phase 5.5: character sheet level-up
  flow, hit die selection, ability score improvement, subclass at
  level 3.
- **Subclass features (level 3+).** Same time as level-up.
- **Pheromone craft / scent abilities / Vocalization / Covenant
  authority resolved.** Phase 6 builds the social/scent layer on top
  of the level-1 stubs Phase 5 lays down.
- **Faction & reputation tracks driving NPC behavior.** Phase 6.
- **Friendly NPC dialogue.** Phase 6 — the merchant/patrol spawns
  Phase 5 ignores become Phase 6's first dialogue subjects.
- **Quest engine + Act I content.** Phase 6.
- **PoI dungeon interiors.** Phase 7.
- **Long/short rest mechanics tied to the world clock.** Phase 8.
- **NPC schedules / day-night activity.** Phase 8.
- **AoE templates (cone, sphere, line).** No level-1 feature in any
  of the 8 classes uses AoE; defer to whatever level introduces the
  first one (mostly subclass features, so naturally Phase 5.5+).
- **Concentration / spell-like effect tracking.** Same — no level-1
  feature concentrates. Defer with subclass implementation.
- **Non-melee/ranged combat:** Phase 5 ships melee + simple ranged
  (single-target, range-band check, line-of-sight). Anything more
  exotic (multi-target rays, area suppression, etc.) waits.

The payoff: Phase 6 starts on a foundation where character + combat are
real and tested, so the social/quest layer can focus on the social/quest
problem instead of co-developing combat at the same time.

---

*Theriapolis Phase 5 Implementation Plan — 2026-04-24*
*Author: Claude (Opus 4.7) for LO, in continuity with the Phase 0–4 plan series.*
