extends RefCounted ## Approved initial weather values A13 (P07-01 SPEC sections 2 and 4). The ## timeline stays a pure state model; this file owns the draw and the effects. const DAY_MINUTES := 1440 const DRAW_MINUTE_OF_DAY := 360 const FIRST_START_MINUTE_OF_DAY := 360 const START_SLOT_MINUTES := 30 const START_SLOTS := 29 ## Rolls 0..99: clear 0-59, rain 60-84, mana fog 85-94, storm 95-99. const CLEAR_BELOW := 60 const RAIN_BELOW := 85 const FOG_BELOW := 95 const DURATION_MINUTES := {&"rain": 360, &"mana_fog": 240, &"storm": 120} const RAIN_ARRIVAL_PERCENT := 50 const RAIN_HOLD_MAX_MINUTES := 240 const FOG_MANA_NUMERATOR := 12 const FOG_MANA_DENOMINATOR := 10 const FOG_INTIMACY_NUMERATOR := 3 const FOG_INTIMACY_DENOMINATOR := 2 static func is_draw_minute(absolute_minute: int) -> bool: return absolute_minute >= 0 and posmod(absolute_minute, DAY_MINUTES) == DRAW_MINUTE_OF_DAY static func day_of(absolute_minute: int) -> int: return int(floor(float(absolute_minute) / DAY_MINUTES)) if absolute_minute >= 0 else -1 ## One draw per day from the saved weather stream. A clear day consumes one ## roll; other days also consume one start-slot roll. static func draw(rng: DeterministicRng, day: int) -> Dictionary: var roll := rng.randi_range(0, 99) var kind: StringName = &"clear" if roll >= FOG_BELOW: kind = &"storm" elif roll >= RAIN_BELOW: kind = &"mana_fog" elif roll >= CLEAR_BELOW: kind = &"rain" if kind == &"clear": return {&"day": day, &"kind": kind, &"roll": roll, &"slot": -1, &"start": -1, &"end": -1} var slot := rng.randi_range(0, START_SLOTS - 1) var start := day * DAY_MINUTES + FIRST_START_MINUTE_OF_DAY + slot * START_SLOT_MINUTES return {&"day": day, &"kind": kind, &"roll": roll, &"slot": slot, &"start": start, &"end": start + int(DURATION_MINUTES[kind])} ## A saved day's draw must be this rule's own result (SPEC section 5.3): the roll ## picks the kind, a clear day has no slot, and any other day starts on its slot ## and lasts its kind's duration. An empty draw (no day drawn yet) is consistent. static func is_consistent_draw(draw: Dictionary) -> bool: if draw.is_empty(): return true var roll := int(draw[&"roll"]) var kind := StringName(draw[&"kind"]) var expected: StringName = &"clear" if roll >= FOG_BELOW: expected = &"storm" elif roll >= RAIN_BELOW: expected = &"mana_fog" elif roll >= CLEAR_BELOW: expected = &"rain" if kind != expected: return false if kind == &"clear": return int(draw[&"slot"]) == -1 and int(draw[&"start"]) == -1 and int(draw[&"end"]) == -1 var slot := int(draw[&"slot"]) var start := int(draw[&"day"]) * DAY_MINUTES + FIRST_START_MINUTE_OF_DAY + slot * START_SLOT_MINUTES return slot >= 0 and slot < START_SLOTS and int(draw[&"start"]) == start \ and int(draw[&"end"]) == start + int(DURATION_MINUTES[kind]) static func arrival_percent(kind: StringName) -> int: return RAIN_ARRIVAL_PERCENT if kind == &"rain" else 100 static func mana_yield(base_amount: int, kind: StringName) -> int: if kind != &"mana_fog": return base_amount return int(floor(float(base_amount * FOG_MANA_NUMERATOR) / FOG_MANA_DENOMINATOR)) static func fog_intimacy_increase(increase: int) -> int: if increase <= 0: return increase return int(floor(float(increase * FOG_INTIMACY_NUMERATOR) / FOG_INTIMACY_DENOMINATOR)) ## Japanese HUD guidance for the current kind (SPEC section 4.6). static func effect_text(kind: StringName) -> String: match kind: &"rain": return "雨: 新規来客が半分になり、帰る予定の客は雨が止むまで(最大4時間)残ります。" &"storm": return "嵐: 新規来客はありません。館内の客は残ります。" &"mana_fog": return "マナ霧: 世界樹のマナ産出が1.2倍、成人確認済みの客の性欲の増え方が1.5倍になります。" return "晴れ: 天候の影響はありません。"