namespace LivingVillage.Desktop open Microsoft.Xna.Framework open LivingVillage.Kernel open LivingVillage.Kernel.Sim type M6DayNight = | Day | Night type M6RenderProfile = { Mode: M6DayNight Background: Color WorldTint: Color Blend: float32 LanternGlow: float32 } module M6Presentation = let dayNightLabel mode : string = match mode with | Day -> "白天" | Night -> "夜晚" /// Continuous light blend: 0 = full day, 1 = full night. Ramps are deterministic /// pure functions of the simulation tick (dawn 4:00-6:00, dusk 20:00-22:00), so the /// boundaries agree with Sim.isNightTick (night >= 22:00 or < 6:00). let lightingBlend (tick: int64) : float32 = let hourTicks = Sim.ticksPerDay / 24L let hour = float32 (tick / hourTicks) let clamp01 v = if v < 0.0f then 0.0f elif v > 1.0f then 1.0f else v if hour >= 6.0f && hour < 20.0f then 0.0f elif hour >= 22.0f then 1.0f elif hour < 4.0f then 1.0f elif hour < 6.0f then clamp01 (1.0f - (hour - 4.0f) / 2.0f) else clamp01 ((hour - 20.0f) / 2.0f) let private lerp (a: int) (b: int) (blend: float32) : int = a + int (float32 (b - a) * blend) let backgroundAtBlend (blend: float32) : Color = Color(lerp 100 18 blend, lerp 160 25 blend, lerp 210 55 blend) let worldTintAtBlend (blend: float32) : Color = Color(lerp 255 118 blend, lerp 248 136 blend, lerp 224 184 blend) /// Pure day-phase lighting: deterministic tint/background plus lantern warm-glow boost. let lightingAtTick (tick: int64) : M6RenderProfile = let blend = lightingBlend tick { Mode = if blend < 0.5f then Day else Night Background = backgroundAtBlend blend Blend = blend WorldTint = worldTintAtBlend blend LanternGlow = blend } let profileAtTick (tick: int64) : M6RenderProfile = lightingAtTick tick let clockLabel (control: SimulationControl) : string = if control.Paused then "暂停" else match control.Speed with | OneX -> "1倍速" | TwoX -> "2倍速" | FiveX -> "5倍速"