P07-01-WX: 仕上がった天候の機能の受入:自然天候・雨・嵐・世界樹と成人客のマナ霧・上部の表示と予報・保存と移行(マナ炉の産出 120 を除く。P07-01 の補助の作業。原単位への加算 0)
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
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])}
|
||||
|
||||
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 "晴れ: 天候の影響はありません。"
|
||||
@@ -0,0 +1 @@
|
||||
uid://bkr55x5to4cgi
|
||||
@@ -2,14 +2,25 @@ extends RefCounted
|
||||
|
||||
## Weather state and scheduled transitions. The caller owns generation policy;
|
||||
## this model invents neither probabilities nor rain/fog balance multipliers.
|
||||
const VERSION := 1
|
||||
## Version 2 also keeps the natural-weather bookkeeping (last daily draw, the
|
||||
## current day's draw) and the once-per-guest rain holds; version 1 snapshots
|
||||
## migrate with no draw for the past and no holds (P07-01 SPEC section 5).
|
||||
const VERSION := 2
|
||||
const LEGACY_VERSION := 1
|
||||
const KINDS := [&"clear", &"rain", &"storm", &"mana_fog"]
|
||||
const MAX_MINUTE := 2147483647
|
||||
const MAX_PENDING := 4096
|
||||
const MAX_RAIN_HOLDS := 100000
|
||||
const DAY_MINUTES := 1440
|
||||
const DRAW_MINUTE_OF_DAY := 360
|
||||
const RAIN_HOLD_MAX_MINUTES := 240
|
||||
var _minute := -1
|
||||
var _kind: StringName = &""
|
||||
var _since := -1
|
||||
var _pending: Array[Dictionary] = []
|
||||
var _last_draw_day := -1
|
||||
var _today_draw: Dictionary = {}
|
||||
var _rain_holds: Dictionary = {}
|
||||
|
||||
func initialize(kind: Variant, minute: Variant) -> bool:
|
||||
if _minute >= 0 or not _valid_kind(kind) or not _valid_minute(minute):
|
||||
@@ -17,6 +28,7 @@ func initialize(kind: Variant, minute: Variant) -> bool:
|
||||
_minute = minute
|
||||
_since = minute
|
||||
_kind = StringName(kind)
|
||||
_last_draw_day = _days_before(minute)
|
||||
return true
|
||||
|
||||
func get_kind() -> StringName:
|
||||
@@ -25,6 +37,9 @@ func get_kind() -> StringName:
|
||||
func get_minute() -> int:
|
||||
return _minute
|
||||
|
||||
func get_since() -> int:
|
||||
return _since
|
||||
|
||||
func permits_new_arrivals() -> bool:
|
||||
return _minute >= 0 and _kind != &"storm"
|
||||
|
||||
@@ -55,13 +70,95 @@ func advance_to(minute: Variant) -> Dictionary:
|
||||
_minute = minute
|
||||
return {&"ok": true, &"replayed": replayed, &"changes": changes}
|
||||
|
||||
## Natural weather (A13): the caller draws once per day at 06:00 and applies
|
||||
## the result here. A start at the current minute switches now; later starts
|
||||
## and the return to clear use the ordinary schedule rules, applied together.
|
||||
func get_last_draw_day() -> int:
|
||||
return _last_draw_day
|
||||
|
||||
func get_today_draw() -> Dictionary:
|
||||
return _today_draw.duplicate(true)
|
||||
|
||||
func apply_draw(draw: Dictionary) -> Dictionary:
|
||||
if _minute < 0 or not _valid_draw(draw):
|
||||
return {&"ok": false, &"code": &"INVALID_DRAW"}
|
||||
var day: int = draw[&"day"]
|
||||
if day <= _last_draw_day or _minute != day * DAY_MINUTES + DRAW_MINUTE_OF_DAY:
|
||||
return {&"ok": false, &"code": &"DRAW_NOT_DUE"}
|
||||
var kind := StringName(draw[&"kind"])
|
||||
var start: int = draw[&"start"]
|
||||
var end: int = draw[&"end"]
|
||||
var changes: Array[Dictionary] = []
|
||||
if kind != &"clear":
|
||||
for row: Dictionary in _pending:
|
||||
if int(row[&"minute"]) == start or int(row[&"minute"]) == end:
|
||||
return {&"ok": false, &"code": &"SCHEDULE_CONFLICT"}
|
||||
if _pending.size() + 2 > MAX_PENDING:
|
||||
return {&"ok": false, &"code": &"SCHEDULE_FULL"}
|
||||
if start == _minute:
|
||||
if kind != _kind:
|
||||
changes.append({&"from": _kind, &"to": kind, &"minute": start})
|
||||
_kind = kind
|
||||
_since = start
|
||||
else:
|
||||
_pending.append({&"kind": kind, &"minute": start})
|
||||
_pending.append({&"kind": &"clear", &"minute": end})
|
||||
_pending.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"minute"] < b[&"minute"])
|
||||
_last_draw_day = day
|
||||
_today_draw = _copy_draw(draw)
|
||||
return {&"ok": true, &"changes": changes}
|
||||
|
||||
## The next scheduled change away from the current kind, for the forecast.
|
||||
func next_change() -> Dictionary:
|
||||
for row: Dictionary in _pending:
|
||||
if row[&"kind"] != _kind:
|
||||
return {&"kind": row[&"kind"], &"minute": row[&"minute"]}
|
||||
return {}
|
||||
|
||||
## When the current rain ends (-1 when it is not raining or no end is known).
|
||||
func rain_end_minute() -> int:
|
||||
if _kind != &"rain":
|
||||
return -1
|
||||
for row: Dictionary in _pending:
|
||||
if row[&"kind"] != &"rain":
|
||||
return int(row[&"minute"])
|
||||
return -1
|
||||
|
||||
func get_rain_hold(guest_id: StringName) -> Dictionary:
|
||||
var row: Variant = _rain_holds.get(String(guest_id), null)
|
||||
return row.duplicate(true) if row is Dictionary else {}
|
||||
|
||||
func get_rain_holds() -> Dictionary:
|
||||
return _rain_holds.duplicate(true)
|
||||
|
||||
func add_rain_hold(guest_id: StringName, original_exit: int, extended_to: int) -> bool:
|
||||
var key := String(guest_id)
|
||||
if key.is_empty() or _rain_holds.has(key) or _rain_holds.size() >= MAX_RAIN_HOLDS \
|
||||
or not _valid_hold({"original_exit": original_exit, "extended_to": extended_to}):
|
||||
return false
|
||||
_rain_holds[key] = {"original_exit": original_exit, "extended_to": extended_to}
|
||||
return true
|
||||
|
||||
## Holds of guests that have left are history; the caller passes who remains.
|
||||
func prune_rain_holds(present_guest_ids: Dictionary) -> void:
|
||||
for key: String in _rain_holds.keys():
|
||||
if not present_guest_ids.has(StringName(key)):
|
||||
_rain_holds.erase(key)
|
||||
|
||||
func create_snapshot() -> Dictionary:
|
||||
return {&"version": VERSION, &"minute": _minute, &"kind": _kind,
|
||||
&"since": _since, &"pending": _pending.duplicate(true)}
|
||||
&"since": _since, &"pending": _pending.duplicate(true),
|
||||
&"natural": {&"last_draw_day": _last_draw_day, &"today": _today_draw.duplicate(true)},
|
||||
&"rain_holds": _rain_holds.duplicate(true)}
|
||||
|
||||
func restore_snapshot(source: Dictionary) -> bool:
|
||||
if not _keys(source, ["version", "minute", "kind", "since", "pending"]) \
|
||||
or typeof(source["version"]) != TYPE_INT or source["version"] != VERSION \
|
||||
var version: Variant = source.get("version", source.get(&"version", null))
|
||||
if typeof(version) != TYPE_INT or version not in [LEGACY_VERSION, VERSION]:
|
||||
return false
|
||||
var expected := ["version", "minute", "kind", "since", "pending"]
|
||||
if version == VERSION:
|
||||
expected = expected + ["natural", "rain_holds"]
|
||||
if not _keys(source, expected) \
|
||||
or not _valid_minute(source["minute"]) or not _valid_minute(source["since"]) \
|
||||
or source["since"] > source["minute"] or not _valid_kind(source["kind"]) \
|
||||
or not source["pending"] is Array or source["pending"].size() > MAX_PENDING:
|
||||
@@ -75,12 +172,75 @@ func restore_snapshot(source: Dictionary) -> bool:
|
||||
return false
|
||||
previous = value["minute"]
|
||||
pending.append({&"kind": StringName(value["kind"]), &"minute": value["minute"]})
|
||||
var last_draw_day := _days_before(source["minute"])
|
||||
var today: Dictionary = {}
|
||||
var holds: Dictionary = {}
|
||||
if version == VERSION:
|
||||
var natural: Variant = source["natural"]
|
||||
if not natural is Dictionary or not _keys(natural, ["last_draw_day", "today"]) \
|
||||
or typeof(natural["last_draw_day"]) != TYPE_INT or natural["last_draw_day"] < -1 \
|
||||
or natural["last_draw_day"] > _day_of(source["minute"]) or not natural["today"] is Dictionary:
|
||||
return false
|
||||
last_draw_day = natural["last_draw_day"]
|
||||
if not natural["today"].is_empty():
|
||||
if not _valid_draw(natural["today"]) or int(natural["today"]["day"]) != last_draw_day:
|
||||
return false
|
||||
today = _copy_draw(natural["today"])
|
||||
var raw_holds: Variant = source["rain_holds"]
|
||||
if not raw_holds is Dictionary or raw_holds.size() > MAX_RAIN_HOLDS:
|
||||
return false
|
||||
for key: Variant in raw_holds:
|
||||
if typeof(key) not in [TYPE_STRING, TYPE_STRING_NAME] or String(key).is_empty() \
|
||||
or not raw_holds[key] is Dictionary or not _keys(raw_holds[key], ["original_exit", "extended_to"]) \
|
||||
or not _valid_hold(raw_holds[key]):
|
||||
return false
|
||||
holds[String(key)] = {"original_exit": int(raw_holds[key]["original_exit"]),
|
||||
"extended_to": int(raw_holds[key]["extended_to"])}
|
||||
_minute = source["minute"]
|
||||
_kind = StringName(source["kind"])
|
||||
_since = source["since"]
|
||||
_pending = pending
|
||||
_last_draw_day = last_draw_day
|
||||
_today_draw = today
|
||||
_rain_holds = holds
|
||||
return true
|
||||
|
||||
## A day whose 06:00 is before the minute counts as drawn, so a new or migrated
|
||||
## timeline never draws for the past (SPEC section 5.2). At exactly 06:00 the
|
||||
## day is still due, which lets a new game draw its opening day.
|
||||
static func _days_before(minute: int) -> int:
|
||||
var day := _day_of(minute)
|
||||
return day if posmod(minute, DAY_MINUTES) > DRAW_MINUTE_OF_DAY else day - 1
|
||||
|
||||
static func _day_of(minute: int) -> int:
|
||||
return int(floor(float(minute) / DAY_MINUTES))
|
||||
|
||||
static func _valid_draw(draw: Variant) -> bool:
|
||||
if not draw is Dictionary or not _keys(draw, ["day", "kind", "roll", "slot", "start", "end"]):
|
||||
return false
|
||||
for key: String in ["day", "roll", "slot", "start", "end"]:
|
||||
if typeof(draw[key]) != TYPE_INT:
|
||||
return false
|
||||
if not _valid_kind(draw["kind"]) or draw["day"] < 0 or draw["roll"] < 0 or draw["roll"] > 99:
|
||||
return false
|
||||
if StringName(draw["kind"]) == &"clear":
|
||||
return draw["slot"] == -1 and draw["start"] == -1 and draw["end"] == -1
|
||||
var day_start: int = draw["day"] * DAY_MINUTES + DRAW_MINUTE_OF_DAY
|
||||
return draw["slot"] >= 0 and draw["start"] >= day_start and draw["start"] <= day_start + 14 * 60 \
|
||||
and draw["end"] > draw["start"] and draw["end"] < day_start + DAY_MINUTES
|
||||
|
||||
static func _copy_draw(draw: Dictionary) -> Dictionary:
|
||||
var result := {}
|
||||
for key: String in ["day", "kind", "roll", "slot", "start", "end"]:
|
||||
result[key] = draw[key] if key != "kind" else String(draw[key])
|
||||
return result
|
||||
|
||||
static func _valid_hold(row: Dictionary) -> bool:
|
||||
var original: Variant = row.get("original_exit", null)
|
||||
var extended: Variant = row.get("extended_to", null)
|
||||
return typeof(original) == TYPE_INT and typeof(extended) == TYPE_INT and original >= 0 \
|
||||
and extended > original and extended <= original + RAIN_HOLD_MAX_MINUTES
|
||||
|
||||
static func _valid_kind(value: Variant) -> bool:
|
||||
return typeof(value) in [TYPE_STRING, TYPE_STRING_NAME] and StringName(value) in KINDS
|
||||
|
||||
|
||||
@@ -365,6 +365,10 @@ func _update_needs_runtime(
|
||||
delta_medical += int(normalized_effects.get(&"medical", 0))
|
||||
delta_leisure += int(normalized_effects.get(&"leisure", 0))
|
||||
delta_intimacy += int(normalized_effects.get(&"intimacy", 0))
|
||||
## A13: mana fog raises an adult-verified guest's positive intimacy increase
|
||||
## to floor(x1.5) = n + floor(n / 2); decreases and other guests are unchanged.
|
||||
if definition.adult_verified and delta_intimacy > 0 and bool(_value(context, &"mana_fog", false)):
|
||||
delta_intimacy += delta_intimacy >> 1
|
||||
|
||||
var after_hunger := clampi(before_hunger + delta_hunger, 0, 100)
|
||||
var after_sleep := clampi(before_sleep + delta_sleep, 0, 100)
|
||||
@@ -1157,7 +1161,7 @@ func _need_context_error(context: Dictionary) -> String:
|
||||
var raw_value: Variant = _value(context, field_id, null)
|
||||
if not raw_value is int or int(raw_value) < 0 or int(raw_value) > 100:
|
||||
return "%s must be an integer from 0 through 100." % field_id
|
||||
for field_id: StringName in [&"is_night", &"work_like_activity", &"unstimulated"]:
|
||||
for field_id: StringName in [&"is_night", &"work_like_activity", &"unstimulated", &"mana_fog"]:
|
||||
if _has_value(context, field_id) and not _value(context, field_id, null) is bool:
|
||||
return "%s must be boolean." % field_id
|
||||
if _has_value(context, &"need_effects"):
|
||||
|
||||
@@ -11,6 +11,10 @@ const CAPACITY := 300
|
||||
const WORLD_TREE_GENERATION := 20
|
||||
const INTERVAL_MINUTES := 10
|
||||
const FURNACE_YIELD := 100
|
||||
## A13 mana fog: world tree and furnace yields x1.2 (floor) at fog boundaries.
|
||||
const FOG_WORLD_TREE_GENERATION := 24
|
||||
const FOG_FURNACE_YIELD := 120
|
||||
const GENERATIONS: Array[int] = [WORLD_TREE_GENERATION, FOG_WORLD_TREE_GENERATION]
|
||||
const LIMIT := 1000000000
|
||||
const FLOW_KEYS: Array[String] = [
|
||||
"from_minute", "to_minute", "before", "generation", "furnace", "demand",
|
||||
@@ -35,12 +39,14 @@ func get_last_flow() -> Dictionary:
|
||||
## Processes every boundary in (last boundary, minute]. Boundaries already
|
||||
## processed are no-ops, so per-minute callers and same-minute retries are safe.
|
||||
## Mana may rise between boundaries (event refunds, commands); anything above
|
||||
## capacity is discarded and recorded as overflow at the next boundary.
|
||||
func advance_to(minute: Variant, before: Variant, demand: Variant) -> Dictionary:
|
||||
## capacity is discarded and recorded as overflow at the next boundary. The
|
||||
## caller passes the world tree yield of the boundary (A13 fog: 24, else 20).
|
||||
func advance_to(minute: Variant, before: Variant, demand: Variant, generation: Variant = WORLD_TREE_GENERATION) -> Dictionary:
|
||||
if _minute < 0:
|
||||
return _failure(&"NOT_STARTED")
|
||||
if typeof(minute) != TYPE_INT or not _amount(before) \
|
||||
or typeof(demand) != TYPE_INT or demand < 0 or demand > LIMIT or minute < _minute:
|
||||
or typeof(demand) != TYPE_INT or demand < 0 or demand > LIMIT or minute < _minute \
|
||||
or typeof(generation) != TYPE_INT or generation not in GENERATIONS:
|
||||
return _failure(&"INVALID_FLOW")
|
||||
var boundary: int = minute - minute % INTERVAL_MINUTES
|
||||
if boundary <= _minute:
|
||||
@@ -49,7 +55,7 @@ func advance_to(minute: Variant, before: Variant, demand: Variant) -> Dictionary
|
||||
var flow := {}
|
||||
var count := 0
|
||||
for to_minute: int in range(_minute + INTERVAL_MINUTES, boundary + 1, INTERVAL_MINUTES):
|
||||
flow = plan(amount, demand, 0, to_minute - INTERVAL_MINUTES, to_minute)
|
||||
flow = plan(amount, demand, 0, to_minute - INTERVAL_MINUTES, to_minute, generation)
|
||||
amount = flow[&"after"]
|
||||
count += 1
|
||||
_minute = boundary
|
||||
@@ -57,17 +63,18 @@ func advance_to(minute: Variant, before: Variant, demand: Variant) -> Dictionary
|
||||
return {&"ok": true, &"processed": true, &"boundaries": count, &"after": amount, &"flow": flow.duplicate(true)}
|
||||
|
||||
## Furnace offers are judged after natural generation and upkeep demand are
|
||||
## paid: base_after = min(C, max(0, B + G - D)); accept only with 100 free.
|
||||
static func furnace_offer_accepted(before: int, demand: int) -> bool:
|
||||
var base_after := mini(CAPACITY, maxi(0, before + WORLD_TREE_GENERATION - demand))
|
||||
return CAPACITY - base_after >= FURNACE_YIELD
|
||||
## paid: base_after = min(C, max(0, B + G - D)); accept only with the yield free
|
||||
## (100, or 120 at an A13 fog boundary where G is 24).
|
||||
static func furnace_offer_accepted(before: int, demand: int, generation: int = WORLD_TREE_GENERATION, furnace_yield: int = FURNACE_YIELD) -> bool:
|
||||
var base_after := mini(CAPACITY, maxi(0, before + generation - demand))
|
||||
return CAPACITY - base_after >= furnace_yield
|
||||
|
||||
static func plan(before: int, demand: int, furnace: int, start: int, end: int) -> Dictionary:
|
||||
var available := before + WORLD_TREE_GENERATION + furnace
|
||||
static func plan(before: int, demand: int, furnace: int, start: int, end: int, generation: int = WORLD_TREE_GENERATION) -> Dictionary:
|
||||
var available := before + generation + furnace
|
||||
var consumed := mini(available, demand)
|
||||
var remaining := available - consumed
|
||||
return {&"from_minute": start, &"to_minute": end, &"before": before,
|
||||
&"generation": WORLD_TREE_GENERATION, &"furnace": furnace, &"demand": demand,
|
||||
&"generation": generation, &"furnace": furnace, &"demand": demand,
|
||||
&"consumed": consumed, &"shortage": demand - consumed,
|
||||
&"overflow": maxi(remaining - CAPACITY, 0), &"after": mini(remaining, CAPACITY)}
|
||||
|
||||
@@ -105,10 +112,10 @@ func restore_snapshot(source: Variant) -> bool:
|
||||
if not _amount(flow[key]):
|
||||
return false
|
||||
if flow["to_minute"] != source["minute"] \
|
||||
or flow["generation"] != WORLD_TREE_GENERATION \
|
||||
or flow["generation"] not in GENERATIONS \
|
||||
or flow["from_minute"] + INTERVAL_MINUTES != flow["to_minute"]:
|
||||
return false
|
||||
var expected := plan(flow["before"], flow["demand"], flow["furnace"], flow["from_minute"], flow["to_minute"])
|
||||
var expected := plan(flow["before"], flow["demand"], flow["furnace"], flow["from_minute"], flow["to_minute"], flow["generation"])
|
||||
if not _same_flow(expected, flow):
|
||||
return false
|
||||
_minute = source["minute"]
|
||||
|
||||
@@ -1013,6 +1013,15 @@ func _process_arrival_boundary(minute: int) -> CommandResult:
|
||||
&"ARRIVAL_PLAN", minute, PHASE_CAPTURE, &"ARRIVAL_CAPTURE_INVALID"
|
||||
)
|
||||
var external_capacity := int(capacity_value)
|
||||
# A13: the arrival boundary reports the weather share of planned arrivals
|
||||
# (rain 50, otherwise 100). Absent means 100 for callers without weather.
|
||||
var percent_value: Variant = capture.get(&"arrival_percent", 100)
|
||||
if not _is_integral_value(percent_value) or int(percent_value) < 0 or int(percent_value) > 100:
|
||||
return _failed_boundary_after_capture(
|
||||
_arrival_boundary, capture_payload, token,
|
||||
&"ARRIVAL_PLAN", minute, PHASE_CAPTURE, &"ARRIVAL_CAPTURE_INVALID"
|
||||
)
|
||||
var arrival_percent := int(percent_value)
|
||||
var remaining := mini(
|
||||
mini(local_capacity, external_capacity),
|
||||
int(_config[&"max_arrivals_per_interval"])
|
||||
@@ -1022,6 +1031,8 @@ func _process_arrival_boundary(minute: int) -> CommandResult:
|
||||
var plans: Array[Dictionary] = []
|
||||
var total_count := 0
|
||||
var capped_rows: Array[Dictionary] = []
|
||||
var accepted_by_row: Array[int] = []
|
||||
var planned_total := 0
|
||||
for row: Dictionary in intent_rows:
|
||||
var accepted := mini(int(row[&"desired_count"]), remaining)
|
||||
var key: String = row[&"key"]
|
||||
@@ -1034,6 +1045,21 @@ func _process_arrival_boundary(minute: int) -> CommandResult:
|
||||
&"guest_class": String(row[&"guest_class"]),
|
||||
&"discarded_numerator": retained - capped_retained,
|
||||
})
|
||||
accepted_by_row.append(accepted)
|
||||
planned_total += accepted
|
||||
remaining -= accepted
|
||||
# Rain keeps floor(planned * percent / 100) arrivals; the dropped arrivals
|
||||
# are lost to the weather and are not carried to the next boundary.
|
||||
var dropped := planned_total - planned_total * arrival_percent / 100
|
||||
var drop_index := accepted_by_row.size() - 1
|
||||
while dropped > 0 and drop_index >= 0:
|
||||
var cut := mini(dropped, accepted_by_row[drop_index])
|
||||
accepted_by_row[drop_index] -= cut
|
||||
dropped -= cut
|
||||
drop_index -= 1
|
||||
for row_index in intent_rows.size():
|
||||
var row: Dictionary = intent_rows[row_index]
|
||||
var accepted := accepted_by_row[row_index]
|
||||
if accepted <= 0:
|
||||
continue
|
||||
if plan_sequence > MAX_SEQUENCE_VALUE \
|
||||
@@ -1058,7 +1084,6 @@ func _process_arrival_boundary(minute: int) -> CommandResult:
|
||||
})
|
||||
plan_sequence += 1
|
||||
total_count += accepted
|
||||
remaining -= accepted
|
||||
|
||||
var payload := capture_payload.duplicate(true)
|
||||
payload[&"plans"] = plans
|
||||
|
||||
Reference in New Issue
Block a user