Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
class_name GuestObservationPanel
|
||||
extends VBoxContainer
|
||||
|
||||
const NEED_NAMES := {&"hunger": "食欲", &"sleep": "睡眠欲", &"leisure": "娯楽欲", &"intimacy": "性欲", &"hygiene": "衛生", &"medical": "救護"}
|
||||
const STATES := ["到着", "入館", "行先を検討", "移動中", "待機中", "利用中", "退館中", "退館", "再検討", "待機終了", "中断", "避難中", "気絶"]
|
||||
const REJECTION_NAMES := {
|
||||
"MISSING_INPUTS": "食材・資材不足", "FACILITY_CLOSED": "閉店・稼働停止", "UNREACHABLE": "経路なし",
|
||||
"CANNOT_PAY": "利用可能な資金が不足", "ETA_AFTER_EXIT": "出発予定に間に合わない",
|
||||
"WAIT_HARD_LIMIT": "待ち時間が長すぎる", "QUALITY_TOO_LOW": "希望する品質に届かない",
|
||||
"RISK_REFUSED": "危険度が高い", "ILLEGALITY_REFUSED": "違法性を敬遠",
|
||||
"MISSING_PERMISSION": "利用資格なし", "FORBIDDEN_GUEST_TAG": "利用対象外",
|
||||
"GUEST_TYPE_NOT_ALLOWED": "利用対象外", "ADULT_NOT_VERIFIED": "年齢条件を満たさない",
|
||||
"CONTENT_FILTERED": "利用設定の対象外", "INVALID_NEED_MAP": "施設設定を確認できない",
|
||||
}
|
||||
var guests: OptionButton
|
||||
var details: Label
|
||||
var decision_label: Label
|
||||
var priority_label: Label
|
||||
var bars: Dictionary = {}
|
||||
var selected_guest_id: StringName = &""
|
||||
var _runtime: Variant
|
||||
var _ids: Array[StringName] = []
|
||||
var _elapsed := 0.0
|
||||
|
||||
func configure(runtime: Variant) -> void:
|
||||
_runtime = runtime
|
||||
|
||||
func _ready() -> void:
|
||||
name = "GuestObservationPanel"
|
||||
custom_minimum_size = Vector2(420, 340)
|
||||
add_theme_constant_override("separation", 8)
|
||||
guests = OptionButton.new()
|
||||
guests.item_selected.connect(func(index: int) -> void:
|
||||
selected_guest_id = StringName(guests.get_item_metadata(index))
|
||||
refresh())
|
||||
add_child(guests)
|
||||
details = _label()
|
||||
for need: StringName in GuestService.PRIMARY_NEEDS:
|
||||
var row := HBoxContainer.new()
|
||||
var title := Label.new()
|
||||
title.text = NEED_NAMES[need]
|
||||
title.custom_minimum_size.x = 85
|
||||
row.add_child(title)
|
||||
var bar := ProgressBar.new()
|
||||
bar.max_value = 100
|
||||
bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_child(bar)
|
||||
bars[need] = bar
|
||||
add_child(row)
|
||||
priority_label = _label()
|
||||
decision_label = _label()
|
||||
_label().text = "欲求が高いほど、そのサービスを優先します。\n利用できる施設の中で欲求を優先し、同じ優先度なら距離・行列・料金などで比較します。\n表示は現在の欲求です。移動・利用の途中で目的地を勝手に変更しません。"
|
||||
refresh()
|
||||
|
||||
func _label() -> Label:
|
||||
var label := Label.new()
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
add_child(label)
|
||||
return label
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not is_visible_in_tree() or not get_window().visible:
|
||||
return
|
||||
_elapsed += delta
|
||||
if _elapsed >= 0.5:
|
||||
_elapsed = 0
|
||||
refresh()
|
||||
|
||||
func refresh() -> void:
|
||||
if guests == null or _runtime == null:
|
||||
return
|
||||
var service: Variant = _runtime.get(&"guest_service")
|
||||
if service == null:
|
||||
return
|
||||
var next: Array[StringName] = []
|
||||
for guest: GuestRuntime in service.get_guests(): next.append(guest.guest_id)
|
||||
if next != _ids:
|
||||
_ids = next
|
||||
guests.clear()
|
||||
for id: StringName in _ids:
|
||||
guests.add_item(String(id))
|
||||
guests.set_item_metadata(guests.item_count-1, id)
|
||||
if not _ids.has(selected_guest_id):
|
||||
selected_guest_id = _ids[0] if not _ids.is_empty() else &""
|
||||
guests.disabled = _ids.is_empty()
|
||||
if not _ids.is_empty(): guests.select(_ids.find(selected_guest_id))
|
||||
var report: Dictionary = service.get_observation(selected_guest_id)
|
||||
for need: StringName in bars:
|
||||
bars[need].value = report.get(&"needs", {}).get(need, 0)
|
||||
if report.is_empty():
|
||||
details.text = "現在、来客はいません。"
|
||||
priority_label.text = ""
|
||||
decision_label.text = ""
|
||||
return
|
||||
var target := String(report[&"target_facility_id"])
|
||||
var facility: Variant = _runtime.facility_service.get_instance(StringName(target))
|
||||
if facility != null:
|
||||
var definition: Variant = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
if definition != null: target = definition.display_name
|
||||
if target.is_empty(): target = "未選択"
|
||||
details.text = "%s · 所持金 %dG · 忍耐 %d\n目的地: %s" % [STATES[int(report[&"state"])], report[&"wallet_gold"], report[&"patience"], target]
|
||||
var names: PackedStringArray = []
|
||||
for need: StringName in report[&"primary_needs"]: names.append(NEED_NAMES[need])
|
||||
priority_label.text = "現在最も高い欲求: " + ("なし" if names.is_empty() else "・".join(names))
|
||||
decision_label.text = _decision_text(_runtime.get_guest_decision_record(selected_guest_id))
|
||||
|
||||
|
||||
func _decision_text(record: Dictionary) -> String:
|
||||
if record.is_empty():
|
||||
return "直近の行先判断:記録なし(判断前、旧セーブ、または履歴の保存範囲外)"
|
||||
var minute := int(record.get(&"absolute_minute", 0))
|
||||
var title := "直近の行先判断 · %d日目 %02d:%02d" % [minute / 1440 + 1, (minute % 1440) / 60, minute % 60]
|
||||
if StringName(record.get(&"action", &"")) == &"LEAVE":
|
||||
return title + "\n条件を満たして選べる施設がなく、退館を選びました。" + _rejection_text(record)
|
||||
var id := StringName(record.get(&"facility_id", &""))
|
||||
var destination := String(id)
|
||||
var facility: Variant = _runtime.facility_service.get_instance(id)
|
||||
if facility != null:
|
||||
var definition: Variant = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
if definition != null: destination = definition.display_name
|
||||
var needs: PackedStringArray = []
|
||||
for need: StringName in record.get(&"primary_needs", []):
|
||||
needs.append("%s %d" % [NEED_NAMES.get(need, String(need)), int(record.get(&"needs", {}).get(need, 0))])
|
||||
return title + "\n選択:%s / 判断時の最大欲求:%s\n移動 %d分・待ち %d分・見込料金 %dG\n選択候補の欲求優先度 %d・比較評価 %d(判断時点)" % [destination, "・".join(needs) if not needs.is_empty() else "なし", int(record.get(&"travel_minutes", 0)), int(record.get(&"wait_minutes", 0)), int(record.get(&"estimated_total_gold", 0)), int(record.get(&"need_priority", 0)), int(record.get(&"utility", 0))] + _rejection_text(record)
|
||||
|
||||
|
||||
func _rejection_text(record: Dictionary) -> String:
|
||||
if not record.has(&"rejection_counts"):
|
||||
return "\n候補の除外理由:この記録にはありません。"
|
||||
var counts: Dictionary = record[&"rejection_counts"]
|
||||
if counts.is_empty():
|
||||
return "\n今回検討した候補には、利用条件による除外はありません。"
|
||||
var reasons: PackedStringArray = []
|
||||
var keys := counts.keys()
|
||||
keys.sort()
|
||||
for reason: String in keys:
|
||||
reasons.append("%s %d施設" % [REJECTION_NAMES.get(reason, "条件を満たさない"), int(counts[reason])])
|
||||
var text := "\n判断時の除外理由:" + "・".join(reasons)
|
||||
for row: Dictionary in record.get(&"rejection_examples", []):
|
||||
var id := StringName(row.get(&"facility_id", &""))
|
||||
var facility: Variant = _runtime.facility_service.get_instance(id)
|
||||
var label := String(id)
|
||||
if facility != null:
|
||||
var definition: Variant = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
if definition != null: label = "%s(%s)" % [definition.display_name, id]
|
||||
text += "\n・%s:%s" % [label, REJECTION_NAMES.get(String(row.get(&"reason", "")), "条件を満たさない")]
|
||||
var omitted: int = int(record.get(&"rejection_count", 0)) - record.get(&"rejection_examples", []).size()
|
||||
if omitted > 0: text += "\nほか%d施設(件数は上記に含みます)" % omitted
|
||||
return text
|
||||
Reference in New Issue
Block a user