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 = 50; // ---- 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; // 0x00E91000.. = equipment (4 slots); 0x00C1A550.. = class traits (Slice 2, permanent). /// 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; } }