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>
This commit is contained in:
Christopher Wiebe
2026-04-30 20:40:51 -07:00
commit b451f83174
525 changed files with 75786 additions and 0 deletions
+489
View File
@@ -0,0 +1,489 @@
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;
}