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:
@@ -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 ~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 < 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;
|
||||
}
|
||||
Reference in New Issue
Block a user