Files
TheriapolisV3/Theriapolis.Core/Constants.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

490 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 ~310 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 25 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;
}