namespace ProjectM.Simulation
{
///
/// Central home for gameplay-balance constants that were previously buried as private consts
/// inside individual systems, so designers have one searchable place to tune them. Burst-safe (compile-time
/// consts only — they inline into the consuming systems with no runtime cost or managed reference).
///
/// Systems reference these via Tuning.* (wired in the 2026-06-04 polish pass, Stage C). When adding a
/// new tunable value, prefer adding it here over a local private const UNLESS it already has an obvious,
/// well-named public home (see the cross-references below) — duplicating a literal creates two sources of truth.
///
///
/// Values that already live in a clear, public, semantically-named home (NOT duplicated here):
///
/// - / — cycle phase durations.
/// - — base→expedition world-space offset.
/// - Per-ability/character stats — authored in ScriptableObjects, baked to the AbilityDatabase blob (M3).
///
///
///
public static class Tuning
{
// ---- Ability damage upgrade (AbilityUpgradeSystem) ----
/// Distinct sentinel SourceId so the upgrade StatModifier is found + grown in place
/// (replace-by-SourceId keeps the bounded modifier buffer from growing a row per upgrade).
public const uint AbilityUpgradeSourceId = 0x00A0E711u;
/// Damage bonus added per upgrade tier (PercentAdd op): +25% per tier.
public const float AbilityUpgradeTierStep = 0.25f;
/// Aether cost charged to the shared ledger per upgrade tier.
public const int AbilityUpgradeCostAmount = 20;
// ---- Resource harvest (ResourceHarvestSystem) ----
/// Effective projectile radius used by the swept-segment node-hit test (added to the node's
/// HitRadius). Tunnel-safe because the segment is reconstructed from Projectile.LastStep.
public const float HarvestProjectileRadius = 0.2f;
// ---- Enemy knockback (ProjectileDamageSystem stamps on hit; EnemyAISystem applies + suppresses seek/strike) ----
/// Knockback speed (world units/sec) a Husk recoils at when shot; 0 disables knockback globally.
public const float KnockbackSpeed = 8f;
/// Server ticks the knockback lasts (~60 ticks/sec).
public const int KnockbackDurationTicks = 8;
// ---- Husk attack telegraph (EnemyAISystem 2-phase strike; client cue in CombatFeedbackSystem) ----
/// Wind-up ticks before a Husk strike lands (~0.37s @ 60 ticks/sec) — sized for a fair tell
/// under interp lag (>= ~250ms reaction + interp buffer; Slice 1 readability). 0/1 = near-instant (legacy).
public const int AttackWindupTicks = 22;
// ---- Production / automation (M7: Harvester/Conveyor/Fabricator) ----
/// Max production cycles a single machine awards in one process (bounds within-session
/// catch-up after any skipped ticks; restore re-seats the baseline so this never reflects wall-clock).
public const int MaxProductionCatchup = 600;
/// EB-2: Charge (turret munition) consumed per turret shot, withdrawn from the global ledger. A
/// turret with 0 Charge SOFT-FAILS (no shot, no cooldown advance). A ledger-fed Fabricator mints Charge from
/// Ore. Operator feel-fork: keep generous so turrets stay fed while you keep mining.
public const int TurretChargeCostPerShot = 1;
/// Max turrets buildable per base (server-enforced in BuildPlaceSystem; the client preview goes red at
/// the cap). Turrets are a deliberate fortress investment, not spammable — paired with the 40-Ore build cost.
public const int TurretCap = 6;
// ---- Cold start (CycleDirectorSpawnSystem seeds the shared ledger on a NEW game) ----
/// DR-042 C6c: Ore deposited into the shared ledger at spawn on a NEW game ONLY (a restored save keeps
/// its persisted ledger). Bootstraps the Fabricator(30)->Charge->Turret(10) chain so a turret placed before any
/// mining isn't a silent cold deadlock. Ore-only so the 'build a Fabricator to arm turrets' lesson survives.
public const int StartingOre = 90;
// ---- Expedition run economy (RoomFieldSystem / RunDirectorSystem) ----
/// Run-wide resource-node allotment: RunDirectorSystem stages it into RunRuntime.NodeBudgetRemaining
/// at the launch edge; RoomFieldSystem floors each room's scatter to what remains and spends it down. THE
/// scarcity knob — ~1.5 nodes/room over an 8-room run (dense Reward rooms drain it fastest; late rooms can
/// run dry, which is the intended tension).
public const int ExpeditionNodeBudget = 12;
/// Boss-room health multiplier over the base enemy prefab (RoomEnemyDirectorSystem spawns ONE
/// boss per Boss room; v1 = a beefed Charger — a real boss kit is a later rung).
public const float BossHealthMultiplier = 8f;
/// Boss-room visual scale multiplier (LocalTransform.Scale is a replicated [GhostField] — set once
/// at spawn on the baked value, legible at a glance).
public const float BossScaleMultiplier = 1.6f;
/// Room-landing grace before the FIRST enemy slot spawns (~1.7 s @ 60): the party just teleported
/// in and needs a beat to orient — without it the wave engages while the camera is still settling (demo
/// polish; an idle player died in ~2 s of landing). Subsequent slots keep the normal drip cadence.
public const uint RoomEntryGraceTicks = 100;
// ---- Expedition BOSS (a scaled Charger given a real kit by BossAISystem; server-only feel consts) ----
/// Radial SLAM AoE radius (world units): a player inside this ring at wind-up elapse eats the hit
/// unless dashing (i-frames). Sized well above melee reach so the tell reads as a boss-scale ring.
public const float BossSlamRadius = 5.5f;
/// Radial SLAM damage. Heavy but survivable given generous i-frames + respawn (readable-but-fair).
public const float BossSlamDamage = 34f;
/// SLAM telegraph lead (~0.75 s @60): a fair dodge window under interp lag before the ring lands.
public const uint BossSlamWindupTicks = 45;
/// Ticks between SLAMs in phase one (~4 s). Phase two multiplies this by BossPhase2SlamCooldownMult.
public const uint BossSlamCooldownTicks = 240;
/// Health fraction at/below which the boss enters phase two (faster + summons adds).
public const float BossPhase2HealthFraction = 0.5f;
/// Phase-two move-speed multiplier over the boss's base EnemyStats.MoveSpeed (a visible gear shift).
public const float BossPhase2SpeedMult = 1.45f;
/// Phase-two SLAM cooldown multiplier (<1 = slams more often when enraged).
public const float BossPhase2SlamCooldownMult = 0.6f;
/// Swarmers summoned per phase-two summon (spawned around the boss, tagged as room zone enemies).
public const int BossSummonCount = 3;
/// Ticks between phase-two summons (~7 s).
public const uint BossSummonCooldownTicks = 420;
/// Cap on live zone enemies the boss will summon toward — it holds fire above this so a boss room
/// never floods past readable (also bounded by the director's MaxAlive count).
public const int BossSummonLiveCap = 8;
// ---- DR-046 room-exit PORTAL / loot window ----
/// Client-side portal position offset from the room origin (where the party landed): a short step
/// back toward the entrance, so 'leave the way you came'. Client VFX + proximity prompt only.
public const float PortalOffsetZ = -5f;
/// How close the local player must be to the portal to show 'E to LEAVE' + send the interact.
public const float PortalInteractRange = 3.5f;
/// RoomExplore soft-timeout (~30 s @60): auto-advance if nobody interacts the portal (no softlock).
public const uint ExploreGraceTicks = 1800;
// ---- Inventory (per-player bag; InventoryMath / ResourceHarvestSystem / InventoryDepositSystem) ----
/// Max stacks a player can carry; InventoryMath rejects deposits past this and the harvest remainder spills to the global ledger.
public const int InventoryMaxSlots = 24;
/// Default per-slot stack cap when an item has no ItemDatabase entry (the catalog is optional at runtime).
public const int DefaultStackMax = 999;
// ---- Equipment stat-mod SourceIds (EquipSystem) ----
// One DISTINCT sentinel per slot: slot i tags its mods with EquipSourceIdBase + i. All of a slot's
// inline mods share that one id and are stripped target-agnostically via
// TimedModifierUtil.RemoveBySourceId on unequip/swap. Full StatModifier SourceId map (keep DISJOINT):
// 0u = pickups + debug-injection; 0x00A0E711 = ability-damage upgrade; 0x00DEB061 = debug stat command;
// 0x00B00000..0x00B10000 = run-scoped BOONS (stripped on return); 0x00C1A550.. = class traits (permanent);
// 0x00E7A000..0x00E7A100 = permanent META upgrades (Step 12a); 0x00E91000.. = equipment (4 slots).
/// Base of the run-scoped BOON SourceId band: each applied pick draws BoonSourceIdBase +
/// (BoonPickCounter++ % BoonSourceIdSpan), so boons stack as distinct rows and ONE range-strip on the
/// Returning edge clears the whole run's boons (the two-channel model — boons never persist).
public const uint BoonSourceIdBase = 0x00B00000u;
/// Width of the boon band [Base, Base+Span) — far above any realistic per-run pick count.
public const uint BoonSourceIdSpan = 0x10000u;
/// DR-046: base PREP-LOADOUT run-scoped SourceId band [Base, Base+Span). DISJOINT from boon
/// (0x00B00000), class (0x00C1A550), meta (0x00E7A000), equip (0x00E91000); one prep option's live
/// StatModifier is keyed PrepSourceIdBase + optionId. Stripped on the Returning edge like boons.
public const uint PrepSourceIdBase = 0x00D00000u;
/// Width of the prep band (far above the tiny option count).
public const uint PrepSourceIdSpan = 0x10000u;
/// Base of the PERMANENT meta-upgrade SourceId band: a purchased tier's live StatModifier is
/// keyed MetaSourceIdBase + UpgradeId (absolute-value UPSERT — one row per owned upgrade, set to
/// ValuePerTier*tier). Persisted via MetaTierState (SaveData v6) and re-applied born-correct at spawn.
/// DISJOINT from every other band; NEVER stripped (the permanent channel of the two-channel model).
public const uint MetaSourceIdBase = 0x00E7A000u;
/// Width of the meta band [Base, Base+Span) — bounds UpgradeId to a byte-sized catalog.
public const uint MetaSourceIdSpan = 0x100u;
/// Base for per-slot equipment SourceIds; slot i tags its mods with EquipSourceIdBase + i.
public const uint EquipSourceIdBase = 0x00E91000u;
/// Slice 2: base SourceId for a class's permanent trait StatModifiers (Warrior/Ranger seeds).
/// DISJOINT from equipment/upgrade/debug ranges; class traits are NEVER stripped (permanent per session).
public const uint ClassSourceId = 0x00C1A550u;
}
}