namespace Theriapolis.Core;

public static class C
{
    // World map (the persistent continental grid)
    public const int WORLD_WIDTH_TILES  = 256;
    public const int WORLD_HEIGHT_TILES = 256;
    public const int WORLD_TILE_PIXELS  = 32;   // px per world tile at 1:1 zoom

    // Macro template (authored skeleton)
    public const int MACRO_GRID_WIDTH  = 32;
    public const int MACRO_GRID_HEIGHT = 32;
    // => each macro cell covers WORLD_WIDTH_TILES/MACRO_GRID_WIDTH world tiles
    //    (currently 256/32 = 8 tiles per cell)

    // Tactical map (streamed)
    public const int TACTICAL_PER_WORLD_TILE   = 32; // 1 tactical tile == 1 world pixel
    public const int TACTICAL_CHUNK_SIZE        = 64; // tactical tiles per chunk side
    public const int TACTICAL_WINDOW_WORLD_TILES = 3; // 3x3 world tiles kept live

    // Generation
    public const int WORLDGEN_BUDGET_SECONDS = 60;

    // Border distortion — coastal domain-warp band (tiles deep on each side of land/ocean boundary)
    public const int COAST_BAND_WIDTH = 12;

    // ElevationGen — continent mask domain-warp amplitude (tiles).
    // Displaces the ellipse falloff coordinates by up to this many tiles,
    // creating organic coastal excursions instead of a smooth ellipse edge.
    public const float COAST_WARP_AMP = 45f;

    // ElevationGen — macro-cell border warp (Addendum A §1 primary mechanism).
    // Displaces the per-tile macro-cell lookup position by a smooth noise
    // field so macro cell boundaries become wiggly curves instead of
    // grid-aligned lines.  MACRO_WARP_AMPLITUDE is the maximum displacement
    // in tiles; MACRO_WARP_FREQUENCY is cycles per tile of the warp noise.
    // With amplitude 24 and frequency 0.012 (period ≈ 83 tiles), macro cell
    // boundaries wobble by up to ~¾ of a cell's width over multi-cell scales,
    // which (combined with the soft macro clamp in ElevationGenStage) is
    // enough to dissolve the grid-aligned rectangular coastlines that the
    // hard clamp used to produce at mountain/ocean interfaces.
    public const float MACRO_WARP_AMPLITUDE = 24f;
    public const float MACRO_WARP_FREQUENCY = 0.012f;

    // RNG sub-stream offset for continent-mask domain warp (must not collide with others)
    public const ulong RNG_COAST_WARP = 0xC4571UL;

    // RNG sub-stream offset for macro-cell border warp
    public const ulong RNG_MACRO_WARP = 0xA1B2C3D4UL;

    // WaterBodyClamp — minimum ocean tiles between continent and map edge
    public const int OCEAN_BORDER_WIDTH = 2;

    // RNG sub-stream offsets (named, never collide)
    public const ulong RNG_TERRAIN   = 0x7E22A11UL;
    public const ulong RNG_MOISTURE  = 0xDEADBEEFUL;
    public const ulong RNG_TEMP      = 0x7E39UL;
    public const ulong RNG_BORDER    = 0xB07DE5UL;
    public const ulong RNG_COAST     = 0xC0A57UL;
    public const ulong RNG_HYDRO     = 0xD7A14A6EUL;
    public const ulong RNG_SETTLE    = 0x5E771EUL;
    public const ulong RNG_ROAD      = 0x7047EUL;
    public const ulong RNG_RAIL      = 0x7A11UL;
    public const ulong RNG_FACTION   = 0xFAC71074UL;
    public const ulong RNG_POI       = 0x901F1UL;
    public const ulong RNG_WEATHER   = 0x4EA7EUL;
    public const ulong RNG_TACTICAL  = 0x7AC71CA1UL;

    // ── Phase 2+3: Additional RNG sub-streams ──────────────────────────────
    public const ulong RNG_LAKE        = 0x1A4EUL;
    public const ulong RNG_MEANDER     = 0xE41DE7UL;
    public const ulong RNG_HABITAT     = 0x4AB17A7UL;
    public const ulong RNG_ANCHOR      = 0xA1C407UL;
    public const ulong RNG_SETTLE_ATTR = 0x5A774UL;
    public const ulong RNG_TRADE       = 0x74ADE5UL;
    public const ulong RNG_ENCOUNTER   = 0xE1C017UL;

    // ── Phase 2: Hydrology ─────────────────────────────────────────────────
    public const int   RIVER_MIN_FLOW_ACCUM     = 1500;  // tiles of upstream catchment to become a river
    public const int   RIVER_MAX_COUNT          = 150;   // hard cap on total rivers to prevent perf explosion
    public const int   RIVER_MAJOR_THRESHOLD    = 8000;  // flow accumulation for "major river"
    public const int   RIVER_MODERATE_THRESHOLD = 2000;  // flow accumulation for "river" (vs stream)
    public const float RIVER_CARVE_DEPTH        = 0.02f; // elevation reduction along river paths
    public const int   LAKE_MIN_AREA            = 12;    // tiles; smaller basins stay dry
    public const float MEANDER_AMP_FLAT         = 5f;    // max world-pixel lateral offset on plains
    public const float MEANDER_AMP_MOUNTAIN     = 1.5f;  // max world-pixel lateral offset in mountains
    public const float MEANDER_FREQ             = 0.08f; // noise frequency for meander offset
    public const int   SPLINE_SUBDIVISIONS      = 4;     // Catmull-Rom subdivisions per control point
    public const float RDP_TOLERANCE            = 2.0f;  // Ramer-Douglas-Peucker LOD tolerance (world px)

    // ── Phase 3: Settlements ───────────────────────────────────────────────
    public const int   SETTLE_TIER1_COUNT    = 1;
    public const int   SETTLE_TIER2_MIN      = 4;
    public const int   SETTLE_TIER2_MAX      = 6;
    public const int   SETTLE_TIER3_MIN      = 15;
    public const int   SETTLE_TIER3_MAX      = 25;
    public const int   SETTLE_TIER4_MIN      = 40;
    public const int   SETTLE_TIER4_MAX      = 80;
    public const int   SETTLE_TIER5_MIN      = 100;
    public const int   SETTLE_TIER5_MAX      = 200;

    // Tile-denominated minimum separations. Halved from their 512×512 baseline
    // (was 120/60/20/8/5 with ANCHOR_MIN_DIST=80) to densify the 256×256 map —
    // at the original spacing, ~60% of the map ran out of room for settlements
    // and Thornfield's constraint region often became unsatisfiable. Preserves
    // the SETTLE_TIER*_MIN/MAX counts, so the smaller world packs the same
    // target settlement population more tightly.
    public const int   SETTLE_MIN_DIST_TIER1 = 60;
    public const int   SETTLE_MIN_DIST_TIER2 = 30;
    public const int   SETTLE_MIN_DIST_TIER3 = 10;
    public const int   SETTLE_MIN_DIST_TIER4 = 4;
    public const int   SETTLE_MIN_DIST_TIER5 = 3;

    public const float ANCHOR_MIN_DIST       = 40f;

    // ── Phase 3: Infrastructure ────────────────────────────────────────────
    public const float ROAD_SHORTCUT_FRACTION = 0.30f;
    public const float BRIDGE_COST            = 50f;
    public const float CROSSING_COST          = 20f;
    public const float SETBACK_COST_SCALE     = 8f;
    public const int   SETBACK_DISTANCE       = 4;
    public const float RAIL_BRIDGE_COST       = 80f;

    // Feature gate for the entire rail subsystem. When false, RailNetworkGenStage
    // early-returns, world.Rails stays empty, HasRail/RailroadAdjacent/RailDir
    // are never set, and no settlement gets HasRailStation = true. Downstream
    // consumers (road costs, cleanup, trade routes, rendering) all handle an
    // empty rail list gracefully, so this flag is a safe on/off switch.
    // static readonly (not const) so the guard evaluates at runtime — avoids
    // CS0162 unreachable-code warnings and prevents stale inlining in
    // downstream assemblies.
    public static readonly bool ENABLE_RAIL   = false;

    // Max deflection angle (degrees) allowed at any vertex of a rail tile path.
    // Heavy rail cars can't corner sharply, so the rail pipeline elides
    // vertices whose turn exceeds this cap when a passable shortcut exists.
    // 45° grid moves give turns of 0°, 45°, 90°, 135° — 75° permits only the
    // first two and forces 90°/135° corners to be smoothed.
    public const float MAX_RAIL_TURN_DEGREES  = 75f;
    public const float EXISTING_ROAD_COST     = 0.1f;  // cost to travel an existing road tile (vs ~3–10 for new terrain)
    public const float EXISTING_RAIL_COST     = 0.1f;  // cost to travel an existing rail tile
    public const int   SETTLEMENT_HALO_RADIUS = 1;     // tiles: no existing-road/rail discount within this Chebyshev distance of path endpoints (prevents fan convergence)
    public const float SETTLEMENT_CONNECT_DIST = 64f;  // world pixels (~2 tiles): max endpoint distance for a settlement to count as visually connected
    public const float BRIDGE_DECK_HALF_LENGTH = 10f;  // world pixels walked along road from crossing to place deck ends

    // ── Phase 3: Polyline Cleanup ─────────────────────────────────────────
    public const float POLYLINE_SNAP_ENDPOINT_DIST = 160f;  // world pixels (~5 tiles): cluster nearby endpoints
    public const float POLYLINE_SNAP_BODY_DIST     = 128f;  // world pixels (~4 tiles): snap endpoint to polyline body (T-junction)
    public const float POLYLINE_MERGE_DIST         = 80f;   // world pixels (~2.5 tiles): merge parallel overlapping segments
    public const int   POLYLINE_MAX_TRIM_POINTS    = 20;    // max points to search when trimming overshoots

    // ── Phase 3: Factions ──────────────────────────────────────────────────
    public const float FACTION_INFLUENCE_RADIUS = 60f;
    public const float FACTION_DECAY_RATE       = 0.015f;

    // ── Phase 3: PoIs ──────────────────────────────────────────────────────
    public const int   POI_MIN_DIST_FROM_SETTLE = 6;
    public const int   POI_MIN_DIST_FROM_POI    = 4;

    // ── Phase 4: Tactical streaming ────────────────────────────────────────
    // Sub-streams of RNG_TACTICAL for the deterministic chunk passes. Each
    // chunk gen pass uses ForSubsystem(worldSeed ^ subStream ^ chunkHash)
    // so adjacent chunks see independent random scatters.
    public const ulong RNG_TACTICAL_GROUND  = 0x7AC71C01UL;
    public const ulong RNG_TACTICAL_SCATTER = 0x7AC71C02UL;
    public const ulong RNG_TACTICAL_SPAWN   = 0x7AC71C03UL;

    // Chunk cache size. The 3×3 world-tile window typically touches at most
    // 9 chunks (each chunk = 2×2 world tiles at 32 tactical-per-world / 64 chunk side).
    // 16 gives a little slack so the player crossing a tile boundary doesn't
    // immediately evict + re-generate a chunk that was just visible.
    public const int   CHUNK_CACHE_SOFT_MAX = 16;

    // ── Phase 4: Actor + clock ─────────────────────────────────────────────
    // Travel time on the world map. With WORLD_TILE_PIXELS=32 and a road,
    // this is 8 * 0.5 = 4 in-game seconds per world pixel ≈ 128 sec/tile.
    // 256-tile world ≈ 32_768 sec across (~9h) on roads, ~18h cross-country.
    public const float BASE_SEC_PER_WORLD_PIXEL = 8f;
    public const float ROAD_SPEED_MULT          = 0.5f;
    public const int   TACTICAL_STEP_SECONDS    = 10;
    public const ulong RNG_ACTOR_ID             = 0xAC704DUL;

    // World-pixel travel speed for the player on the world map (pixels per
    // second of real time). Independent of the in-game clock advancement;
    // this just controls how fast the sprite slides along the path.
    public const float PLAYER_TRAVEL_PX_PER_SEC = 80f;

    // Tactical-mode WASD movement speed in tactical tiles per real second.
    // Continuous (sub-pixel) motion replaces the discrete one-tile step that
    // looked chunky at high zoom — at CAMERA_MAX_ZOOM=16, a 1-tile jump was
    // a visible 16-screen-pixel hop; now the player slides smoothly.
    // 40 px/sec is brisk-walk pace: at zoom 16 that's 640 screen px/sec
    // (half the window width per second), at zoom 3 (tactical threshold)
    // it's 120 screen px/sec.
    public const float TACTICAL_PLAYER_PX_PER_SEC = 3f;

    // ── Phase 4: Save (bumped to v8 in Phase 7 M0) ────────────────────────
    public const int   SAVE_SCHEMA_VERSION      = 8;
    public const int   SAVE_SLOT_COUNT          = 10;
    /// <summary>
    /// Minimum readable save version. Below this, the loader refuses with
    /// "this save predates Phase 5 — start a new game from the same seed".
    /// Bump only when older saves can no longer be migrated meaningfully.
    /// v5 is still accepted (Phase 6 M2 adds an additive V5→V6 migration).
    /// </summary>
    public const int   SAVE_SCHEMA_MIN_VERSION  = 5;

    // ── Phase 4: Player marker ─────────────────────────────────────────────
    // Player marker diameter, in *screen* pixels. The sprite renderer
    // counter-scales by 1/Zoom so the marker stays this size at every zoom
    // level — visible-but-not-huge on the world map, and not screen-filling
    // when the player walks around in tactical at CAMERA_MAX_ZOOM.
    public const int   PLAYER_MARKER_SCREEN_PX  = 48;

    // ── Phase 4: Camera / zoom ─────────────────────────────────────────────
    // Zoom = screen pixels per world pixel. At Zoom=1, one tactical tile is
    // one screen pixel; world tiles (32 world px wide) span 32 screen pixels.
    //
    // CAMERA_TACTICAL_THRESHOLD = 32 — tactical kicks in exactly when each
    // tactical tile maps to TACTICAL_TILE_SPRITE_PX screen pixels, so the
    // 32×32 sprite art renders 1:1 at the threshold and upscales smoothly
    // up to CAMERA_MAX_ZOOM (3.125× upscale at full zoom).
    //
    // CAMERA_MAX_ZOOM = 100 — at 1280px window that's ~12.8 tactical tiles
    // visible across the screen at max zoom: comfortable for inspecting an
    // individual building or NPC.
    public const float CAMERA_MIN_ZOOM           = 0.01f;
    public const float CAMERA_MAX_ZOOM           = 100.0f;
    public const float CAMERA_TACTICAL_THRESHOLD = 32.0f;

    // ── Phase 4: Tactical art ──────────────────────────────────────────────
    // Source resolution of every tactical tile sprite (surface + decoration).
    // The renderer scales each 32×32 source down to a 1×1 world-pixel cell so
    // the existing coord system ("1 tactical tile = 1 world pixel") stays
    // intact. With CAMERA_TACTICAL_THRESHOLD = 32, the sprite displays at
    // native 1:1 the moment tactical mode engages.
    public const int   TACTICAL_TILE_SPRITE_PX = 32;

    // ── Phase 5: RNG sub-streams ───────────────────────────────────────────
    public const ulong RNG_CHARACTER = 0xC4A2AC7EUL;  // character creation rolls + starting equipment
    public const ulong RNG_STAT_ROLL = 0x57A7507UL;   // 4d6-drop-lowest re-rolls in char creation
    public const ulong RNG_COMBAT    = 0xC0B47UL;     // per-encounter dice
    public const ulong RNG_NPC_SPAWN = 0xA7C2AUL;     // per-NPC variation when instantiating from chunk spawn list
    public const ulong RNG_LOOT      = 0x107EUL;      // post-encounter drops

    // ── Phase 5: Encounter triggering ─────────────────────────────────────
    // Hostile NPCs auto-trigger combat on LOS within this radius.
    public const int   ENCOUNTER_TRIGGER_TILES   = 8;
    // Friendly / Neutral NPCs show "[F] Talk to ..." prompt within this radius.
    public const int   INTERACT_PROMPT_TILES     = 2;
    // Encounter ends when all hostiles are out of sight + this far for this many turns.
    public const int   ENCOUNTER_DISENGAGE_TILES = 16;
    public const int   ENCOUNTER_DISENGAGE_TURNS = 3;

    // ── Phase 5: Combat resolver ──────────────────────────────────────────
    public const int   AC_FLOOR              = 5;
    public const int   AC_CEILING            = 30;
    public const int   HP_MAX                = 999;
    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) ────────────────────────
    // Per-chunk DangerZone (0..4) drives which template each SpawnKind picks.
    public const int   DANGER_DIST_FROM_START_PER_ZONE   = 50;  // tiles per +1 zone increment
    public const int   DANGER_DIST_FROM_ROAD_THRESHOLD   = 8;   // further than this = +1 zone
    public const int   DANGER_DIST_FROM_SETTLE_THRESHOLD = 16;  // further than this = +1 zone
    public const int   DANGER_ZONE_MIN = 0;
    public const int   DANGER_ZONE_MAX = 4;

    // ── Phase 5: Save (will bump SAVE_SCHEMA_VERSION to 5 in M2) ──────────
    public const string SAVE_SLOT_AUTOSAVE_COMBAT = "autosave_combat";

    // ── Phase 6 M0: Settlement stamping ───────────────────────────────────
    /// <summary>SeededRng sub-stream for procedural Tier 2–5 layout rolls.</summary>
    public const ulong RNG_BUILDING_LAYOUT = 0xB1D106UL;

    /// <summary>Smallest acceptable footprint dimension for a building template.</summary>
    public const int   BUILDING_MIN_W_TILES         = 4;
    public const int   BUILDING_MIN_H_TILES         = 3;

    /// <summary>Don't stamp scatter or walls within this halo of any door tile.</summary>
    public const int   BUILDING_DOOR_HALO_TILES     = 2;

    /// <summary>Minimum gap (in tiles) between adjacent procedural buildings.</summary>
    public const int   SETTLEMENT_BUILDING_GAP_MIN  = 2;

    // ── Phase 6 M2: Reputation ────────────────────────────────────────────
    /// <summary>Minimum / maximum reputation value (per-faction and per-NPC personal disposition).</summary>
    public const int   REP_MIN                        = -100;
    public const int   REP_MAX                        =  100;

    // Disposition tier thresholds — *inclusive lower bound* of each band so
    // a single ascending if-cascade picks them out via `score >= threshold`.
    // Per reputation.md §I-2 the bands are -100..-76 Nemesis, -75..-51
    // Hostile, -50..-26 Antagonistic, -25..-1 Unfriendly, 0 Neutral,
    // +1..+25 Favorable, +26..+50 Friendly, +51..+75 Allied, +76..+100 Champion.
    public const int   REP_HOSTILE_THRESHOLD          = -75;
    public const int   REP_ANTAGONISTIC_THRESHOLD     = -50;
    public const int   REP_UNFRIENDLY_THRESHOLD       = -25;
    public const int   REP_FAVORABLE_THRESHOLD        =   1;
    public const int   REP_FRIENDLY_THRESHOLD         =  26;
    public const int   REP_ALLIED_THRESHOLD           =  51;
    public const int   REP_CHAMPION_THRESHOLD         =  76;

    // ── Phase 6 M5: Reputation propagation ────────────────────────────────
    /// <summary>SeededRng sub-stream for propagation coin-flips (frontier delivery).</summary>
    public const ulong RNG_REP_PROPAGATION            = 0x1EFA77UL;

    // Distance-band radii in world tiles. Zero (origin) = full magnitude.
    public const int   REP_ADJACENT_DIST_TILES        =  20;
    public const int   REP_REGIONAL_DIST_TILES        =  60;
    public const int   REP_CONTINENTAL_DIST_TILES     = 200;

    // Decay multipliers per band, expressed as integer percentages.
    public const int   REP_DECAY_AT_ORIGIN_PCT        = 100;
    public const int   REP_DECAY_ADJACENT_PCT         =  80;
    public const int   REP_DECAY_REGIONAL_PCT         =  60;
    public const int   REP_DECAY_CONTINENTAL_PCT      =  40;
    public const int   REP_DECAY_FRONTIER_PCT         =  20;

    /// <summary>Coin-flip probability that a frontier settlement actually receives the news at all.</summary>
    public const int   REP_FRONTIER_DELIVERY_PROB_PCT =  50;

    /// <summary>
    /// Magnitudes at or above this threshold (positive or negative) bypass
    /// distance decay AND frontier coin-flips: NEMESIS / CHAMPION events
    /// propagate at full magnitude, continent-wide, immediately. Per
    /// reputation.md §I-2.
    /// </summary>
    public const int   REP_EXTREME_BYPASS_MAGNITUDE   = 50;

    // ── Phase 6 M3: Dialogue ──────────────────────────────────────────────
    /// <summary>SeededRng sub-stream for in-dialogue skill-check rolls.</summary>
    public const ulong RNG_DIALOGUE                   = 0xD1A106EUL;

    /// <summary>Cap on options shown per dialogue node.</summary>
    public const int   DIALOGUE_MAX_OPTIONS_PER_NODE  = 6;

    /// <summary>Lines of scrollback retained inside the dialogue panel.</summary>
    public const int   DIALOGUE_HISTORY_LINES         = 50;

    // ── Phase 6 M4: Quests ────────────────────────────────────────────────
    /// <summary>SeededRng sub-stream for quest-step random outcomes.</summary>
    public const ulong RNG_QUEST                       = 0x9E57E0UL;

    /// <summary>Sanity cap on simultaneously active quests.</summary>
    public const int   QUEST_MAX_ACTIVE                = 20;

    /// <summary>Cap on completed-quest history shown in the journal.</summary>
    public const int   QUEST_LOG_COMPLETED_LIMIT       = 100;

    /// <summary>Tile radius for "enter_anchor" quest triggers (matches plan §4.4).</summary>
    public const int   QUEST_ENTER_ANCHOR_RADIUS_TILES = 4;

    /// <summary>Tile radius for "enter_role_proximity" quest triggers.</summary>
    public const int   QUEST_ENTER_ROLE_RADIUS_TILES   = 2;

    // ── Phase 6.5 M0: Levelling ───────────────────────────────────────────
    /// <summary>SeededRng sub-stream for HP rolls and other per-level random outcomes.</summary>
    public const ulong RNG_LEVELUP                     = 0x1E7E107UL;

    /// <summary>
    /// Levels that grant an Ability Score Improvement choice (player picks
    /// +2 to one stat or +1 to two stats). Standard d20 schedule.
    /// The XP-to-next-level table itself lives in
    /// <see cref="Rules.Stats.XpTable.Threshold"/> (canonical, 1-indexed).
    /// </summary>
    public static readonly int[] ASI_LEVELS = new[] { 4, 8, 12, 16, 19 };

    /// <summary>Level at which the player picks a subclass.</summary>
    public const int   SUBCLASS_SELECTION_LEVEL = 3;

    /// <summary>Hard cap on ability scores below level 20.</summary>
    public const int   ABILITY_SCORE_CAP_PRE_L20 = 20;

    /// <summary>Hard cap on ability scores at level 20 (a couple of capstone features push this).</summary>
    public const int   ABILITY_SCORE_CAP_AT_L20 = 22;

    /// <summary>
    /// Maximum supported character level. Phase 6.5 wires the engine for
    /// 1..20 but only ships full mechanical effect for levels 1..15;
    /// levels 16..20 use the same machinery but feature-defs may stub.
    /// </summary>
    public const int   CHARACTER_LEVEL_MAX = 20;

    // ── Phase 6.5 M5: Hybrid passing detection ────────────────────────────
    /// <summary>SeededRng sub-stream for hybrid scent-detection rolls.</summary>
    public const ulong RNG_PASSING                     = 0x9A55E5UL;

    /// <summary>WIS save DC the NPC rolls to detect a hybrid PC's scent.</summary>
    public const int   HYBRID_DETECTION_DC             = 12;

    /// <summary>
    /// CHA Deception DC the PC's "I'm passing" counter-roll uses. Standard
    /// case: equal to the NPC's detection DC. <c>theriapolis-rpg-clades.md</c>
    /// notes a stricter DC for an even split — Phase 6.5 simplification:
    /// dominant lineage is always considered ≥ 80% expressive (the player
    /// chooses dominance and accepts that consequence).
    /// </summary>
    public const int   HYBRID_DECEPTION_DC             = 12;

    // ── Phase 6.5 M7: Betrayal cascade magnitudes ─────────────────────────
    /// <summary>
    /// Threshold magnitude (inclusive) for a "minor" betrayal. Drives the
    /// cascade tier in <see cref="Rules.Reputation.BetrayalCascade"/> —
    /// any <see cref="Rules.Reputation.RepEventKind.Betrayal"/> with
    /// <c>magnitude &lt; 0</c> picks the most severe matching tier
    /// (most-negative wins).
    /// </summary>
    public const int   BETRAYAL_MAGNITUDE_MINOR        = -10;
    public const int   BETRAYAL_MAGNITUDE_MODERATE     = -25;
    public const int   BETRAYAL_MAGNITUDE_MAJOR        = -50;
    public const int   BETRAYAL_MAGNITUDE_CRITICAL     = -75;

    /// <summary>Faction-standing impact at each betrayal tier (signed; cascade through opposition).</summary>
    public const int   BETRAYAL_FACTION_DELTA_MINOR    =  -5;
    public const int   BETRAYAL_FACTION_DELTA_MODERATE = -15;
    public const int   BETRAYAL_FACTION_DELTA_MAJOR    = -30;
    public const int   BETRAYAL_FACTION_DELTA_CRITICAL = -50;

    // ── Phase 7: RNG sub-streams ──────────────────────────────────────────
    /// <summary>Per-PoI dungeon room-graph generation: room count, branching, special-room slots.</summary>
    public const ulong RNG_DUNGEON_LAYOUT   = 0xD06E07AUL;
    /// <summary>Within a layout, picking which room template fills each role-eligible slot.</summary>
    public const ulong RNG_ROOM_PICK        = 0x40072EUL;
    /// <summary>Per-room spawn selection (which NPC template fills each encounter slot).</summary>
    public const ulong RNG_DUNGEON_POPULATE = 0x70757UL;
    /// <summary>Per-container loot rolls. Distinct from <see cref="RNG_LOOT"/> (encounter drops).</summary>
    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;
    /// <summary>Reject-and-retry ceiling on the layout-builder. Beyond this we fall back to a guaranteed-valid linear layout.</summary>
    public const int   DUNGEON_LAYOUT_MAX_ATTEMPTS = 8;

    /// <summary>Rooms snap their AABB to a multiple of this many tactical tiles.</summary>
    public const int   ROOM_GRID_SNAP_TILES     = 16;
    public const int   ROOM_CORRIDOR_MIN_W      = 2;
    public const int   ROOM_CORRIDOR_MAX_W      = 3;
    /// <summary>Minimum gap (in tiles) between adjacent rooms before a corridor is required.</summary>
    public const int   ROOM_INTER_ROOM_GAP_TILES = 2;

    /// <summary>Tactical-tile padding around the room-AABB union when sizing the dungeon's tile array.</summary>
    public const int   DUNGEON_AABB_PADDING     = 8;

    // ── Phase 7: Loot band selection (PoI LevelBand → loot table tier) ────
    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 multipliers ────────────────────
    /// <summary>Soft mismatch (cervid antler clearance for Large PCs, bovid space for Small PCs).</summary>
    public const float MOVE_COST_MISMATCH_LIGHT  = 1.2f;
    /// <summary>Medium mismatch (Med-Large PCs in Mustelid tunnels, Small PCs in Ursid halls).</summary>
    public const float MOVE_COST_MISMATCH_MED    = 1.5f;
    /// <summary>Heavy mismatch / squeezing (Large PCs in Mustelid tunnels — the canonical example).</summary>
    public const float MOVE_COST_MISMATCH_HEAVY  = 2.0f;

    // ── Phase 7: Locked door + trap DCs ───────────────────────────────────
    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;

    /// <summary>Tripwire trap damage on disarm-fail: 1d6 piercing.</summary>
    public const int   TRAP_DAMAGE_DICE_TRIPWIRE = 1;
    public const int   TRAP_DAMAGE_DIE_TRIPWIRE  = 6;

    /// <summary>
    /// Bonus XP awarded on full dungeon clear, expressed as a multiplier of
    /// the dungeon's largest single-NPC XpAward. 1.0 means "doubling the
    /// boss kill". Tunable post-playtest.
    /// </summary>
    public const float DUNGEON_CLEAR_XP_BONUS_FRACTION = 1.0f;
}
