P07-01-WX: 仕上がった天候の機能の受入:自然天候・雨・嵐・世界樹と成人客のマナ霧・上部の表示と予報・保存と移行(マナ炉の産出 120 を除く。P07-01 の補助の作業。原単位への加算 0)

This commit is contained in:
Agent Control Runner
2026-10-24 07:08:23 +09:00
parent c50136017b
commit f99c881bca
11 changed files with 370 additions and 42 deletions
@@ -29,6 +29,7 @@ func _run() -> void:
_test_daily_natural_draw()
_test_rain_arrivals()
_test_rain_stay_and_saves()
_test_rain_hold_exit()
_test_storm()
_test_mana_fog()
_test_migration_and_rejection()
@@ -227,6 +228,24 @@ func _test_rain_stay_and_saves() -> void:
_expect(clear_runtime.weather.schedule(&"storm", 361)[&"ok"], "schedule a storm")
clear_runtime.weather.advance_to(361)
_expect(not clear_runtime.call("_hold_guest_for_rain", dry, 361), "no hold in a storm")
var fog_runtime: Variant = _new_runtime(SEED_FOG)[&"runtime"]
var misty: Variant = _spawn(fog_runtime, &"p07_fog_due_guest", &"traveler", 361)
_expect(fog_runtime.weather.get_kind() == &"mana_fog" and not fog_runtime.call("_hold_guest_for_rain", misty, 361) and misty.planned_exit_at == 361, "no hold in mana fog")
## F3: after its hold the guest leaves by the current rule. A guest without a
## suitable facility leaves at its next decision, so the ordinary decision step
## itself runs at the extended time: it is not held again and begins leaving
## with the ordinary PLANNED_EXIT.
func _test_rain_hold_exit() -> void:
var rainy := _new_runtime(SEED_RAIN)
var runtime: Variant = rainy[&"runtime"]
var guest: Variant = _spawn(runtime, &"p07_exit_guest", &"traveler", 362)
rainy[&"clock"].advance_minutes(2)
_expect(guest.state == GuestRuntime.State.DECIDING and runtime.weather.get_kind() == &"rain", "the guest decides at 06:02 in rain")
_expect(runtime.call("_hold_guest_for_rain", guest, 362) and guest.planned_exit_at == 602 and runtime.weather.get_rain_hold(&"p07_exit_guest") == {"original_exit": 362, "extended_to": 602}, "the rain holds the guest due at 06:02 to 10:02")
runtime.call("_advance_guest_ai_decisions", 602)
_expect(guest.exit_reason == &"PLANNED_EXIT" and guest.state == GuestRuntime.State.LEAVING and guest.planned_exit_at == 602, "at 10:02 the held guest's decision begins leaving with PLANNED_EXIT (%s)" % String(guest.exit_reason))
## F4: natural storm keeps zero new arrivals.
@@ -313,6 +332,16 @@ func _test_migration_and_rejection() -> void:
var overlap := saved.duplicate(true)
overlap[&"systems"][&"runtime"][&"weather"][&"pending"] = [{"kind": "storm", "minute": 700}, {"kind": "clear", "minute": 700}]
_expect(not runtime.restore_snapshot(overlap) and runtime.create_snapshot() == migrated, "overlapping weather changes are refused atomically")
# The saved rain draw of seed 1106 (roll 60-84, slot 0, 06:00-12:00) changed in one field
# each: a roll of a clear day, a slot that does not match the start, a wrong duration.
var today: Dictionary = saved[&"systems"][&"runtime"][&"weather"][&"natural"][&"today"]
_expect(NaturalWeather.is_consistent_draw(today) and String(today["kind"]) == "rain" and int(today["slot"]) == 0, "the saved opening draw follows the draw rule")
for change: Dictionary in [{"roll": 10}, {"slot": 1}, {"end": 700}]:
var inconsistent := saved.duplicate(true)
var drawn: Dictionary = inconsistent[&"systems"][&"runtime"][&"weather"][&"natural"][&"today"]
for key: String in change:
drawn[key] = change[key]
_expect(not runtime.restore_snapshot(inconsistent) and runtime.create_snapshot() == migrated, "an internally inconsistent saved draw is refused atomically (%s)" % str(change))
## F6: the ordinary Main HUD shows the weather, the forecast and the effect guidance.
@@ -339,6 +368,19 @@ func _test_hud_forecast() -> void:
clock.advance_minutes(360)
main.call("_refresh_dashboard")
_expect(weather_label.text == "晴れ" and forecast_label.text == "次: 予定なし" and "影響はありません" in weather_label.tooltip_text, "HUD follows the end of the rain")
var now: int = clock.absolute_minute
_expect(runtime.weather.schedule(&"storm", now + 1)[&"ok"] and runtime.weather.schedule(&"mana_fog", now + 2)[&"ok"], "schedule a storm and then mana fog on the ordinary Runtime")
clock.advance_minutes(1)
main.call("_refresh_dashboard")
report = runtime.get_weather_report()
var fog_minute := posmod(now + 2, 1440)
_expect(weather_label.text == "嵐" and report[&"kind"] == &"storm" and String(report[&"effect_text"]) in weather_label.tooltip_text and "新規来客はありません" in weather_label.tooltip_text, "HUD shows the storm and its guidance")
_expect(forecast_label.text == "次: マナ霧 %02d:%02d" % [fog_minute / 60, fog_minute % 60], "HUD forecast shows the next change to mana fog (%s)" % forecast_label.text)
clock.advance_minutes(1)
main.call("_refresh_dashboard")
report = runtime.get_weather_report()
_expect(weather_label.text == "マナ霧" and report[&"kind"] == &"mana_fog" and String(report[&"effect_text"]) in weather_label.tooltip_text
and "1.2倍" in weather_label.tooltip_text and "1.5倍" in weather_label.tooltip_text and not "炉" in weather_label.tooltip_text, "HUD shows the mana fog and its guidance without a furnace claim")
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, main)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")