blob: 11399c009287b921a8c748fe51e4548f9dee1f47 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
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
SkyWarmth: float32 }
module M6Presentation =
let dayNightLabel mode : string =
match mode with
| Day -> "白天"
| Night -> "夜晚"
// Transition windows in float hours. Dawn lingers past 6:00 and dusk begins before
// 20:00 so the blue hour is longer and the sky warms before it darkens; the anchors
// still agree with Sim.isNightTick (night >= 22:00 or < 6:00) once fully dark.
let dawnStartHour = 4.0f
let dawnEndHour = 6.5f
let duskStartHour = 19.5f
let duskEndHour = 22.0f
/// Continuous light blend: 0 = full day, 1 = full night. Deterministic pure function
/// of the simulation tick, parameterised by the dawn/dusk windows above.
let lightingBlend (tick: int64) : float32 =
let hourTicks = Sim.ticksPerDay / 24L
let hour = float32 tick / float32 hourTicks
let clamp01 v = if v < 0.0f then 0.0f elif v > 1.0f then 1.0f else v
if hour >= dawnEndHour && hour < duskStartHour then 0.0f
elif hour >= duskEndHour || hour < dawnStartHour then 1.0f
elif hour < dawnEndHour then clamp01 (1.0f - (hour - dawnStartHour) / (dawnEndHour - dawnStartHour))
else clamp01 ((hour - duskStartHour) / (duskEndHour - duskStartHour))
/// Golden-hour warmth (0..1): a triangular bump peaking mid-dawn and mid-dusk, zero at
/// noon and midnight. Drives the warm sky/world tint so P17 transition frames read golden.
let transitionWarmth (tick: int64) : float32 =
let hour = float32 tick / float32 (Sim.ticksPerDay / 24L)
let bump (startHour: float32) (endHour: float32) : float32 =
if hour <= startHour || hour >= endHour then
0.0f
else
let mid = (startHour + endHour) * 0.5f
let half = (endHour - startHour) * 0.5f
1.0f - abs (hour - mid) / half
max (bump dawnStartHour dawnEndHour) (bump duskStartHour duskEndHour)
let private lerp (a: int) (b: int) (blend: float32) : int =
a + int (float32 (b - a) * blend)
/// Blended sky/world colour plus the golden-hour warmth shift (warm channels up, blue down).
let backgroundAtWarmth (blend: float32) (warmth: float32) : Color =
Color(
lerp 100 18 blend + int (52.0f * warmth),
lerp 160 25 blend + int (14.0f * warmth),
lerp 210 55 blend - int (34.0f * warmth))
let worldTintAtWarmth (blend: float32) (warmth: float32) : Color =
Color(
lerp 255 118 blend + int (10.0f * warmth),
lerp 248 136 blend - int (6.0f * warmth),
lerp 224 184 blend - int (26.0f * warmth))
/// Warmth-free base colours (kept for callers that only have a blend value).
let backgroundAtBlend (blend: float32) : Color = backgroundAtWarmth blend 0.0f
let worldTintAtBlend (blend: float32) : Color = worldTintAtWarmth blend 0.0f
/// Pure day-phase lighting: deterministic tint/background/warmth plus lantern glow.
let lightingAtTick (tick: int64) : M6RenderProfile =
let blend = lightingBlend tick
let warmth = transitionWarmth tick
{ Mode = if blend < 0.5f then Day else Night
Background = backgroundAtWarmth blend warmth
Blend = blend
WorldTint = worldTintAtWarmth blend warmth
LanternGlow = blend
SkyWarmth = warmth }
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倍速"
/// 倍速标签(语义别名,HUD 用)。
let speedLabel (control: SimulationControl) : string = clockLabel control
let private hourOf (tick: int64) : int =
int ((tick / (Sim.ticksPerDay / 24L)) % 24L)
/// 24 小时制的模拟时钟(HH:MM),由 tick 纯函数导出。
let clockTimeLabel (tick: int64) : string =
let hourTicks = Sim.ticksPerDay / 24L
let hour = hourOf tick
let minute = int ((tick % hourTicks) * 60L / hourTicks)
sprintf "%02d:%02d" hour minute
/// 时段标签:清晨/白天/黄昏/夜晚,与实际 tick 一致(纯函数)。
let timePeriodLabel (tick: int64) : string =
let hour = hourOf tick
if hour < 5 then "夜晚"
elif hour < 8 then "清晨"
elif hour < 17 then "白天"
elif hour < 20 then "黄昏"
else "夜晚"
|